You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2013/01/06 00:35:45 UTC

svn commit: r1429425 - in /libcloud/trunk: ./ libcloud/dns/ libcloud/dns/drivers/ libcloud/test/ libcloud/test/dns/ libcloud/test/dns/fixtures/route53/

Author: tomaz
Date: Sat Jan  5 23:35:44 2013
New Revision: 1429425

URL: http://svn.apache.org/viewvc?rev=1429425&view=rev
Log:
Add / finish Route53 driver (add all of the non-read functionality and tests).

Contributed by John Carr, part of LIBCLOUD-162.

Added:
    libcloud/trunk/libcloud/test/dns/fixtures/route53/
    libcloud/trunk/libcloud/test/dns/fixtures/route53/create_zone.xml
    libcloud/trunk/libcloud/test/dns/fixtures/route53/get_zone.xml
    libcloud/trunk/libcloud/test/dns/fixtures/route53/invalid_change_batch.xml
    libcloud/trunk/libcloud/test/dns/fixtures/route53/list_records.xml
    libcloud/trunk/libcloud/test/dns/fixtures/route53/list_zones.xml
    libcloud/trunk/libcloud/test/dns/fixtures/route53/record_does_not_exist.xml
    libcloud/trunk/libcloud/test/dns/fixtures/route53/zone_does_not_exist.xml
    libcloud/trunk/libcloud/test/dns/test_route53.py
Modified:
    libcloud/trunk/CHANGES
    libcloud/trunk/libcloud/dns/drivers/route53.py
    libcloud/trunk/libcloud/dns/providers.py
    libcloud/trunk/libcloud/test/secrets.py-dist

Modified: libcloud/trunk/CHANGES
URL: http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1429425&r1=1429424&r2=1429425&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Sat Jan  5 23:35:44 2013
@@ -133,6 +133,9 @@ Changes with Apache Libcloud in developm
     - New driver for HostVirtual provider (www.vr.org). (LIBCLOUD-249)
       [Dinesh Bhoopathy]
 
+    - Finish Amazon Route53 driver. (LIBCLOUD-132)
+      [John Carr]
+
 Changes with Apache Libcloud 0.11.4:
 
   *) General

Modified: libcloud/trunk/libcloud/dns/drivers/route53.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/drivers/route53.py?rev=1429425&r1=1429424&r2=1429425&view=diff
==============================================================================
--- libcloud/trunk/libcloud/dns/drivers/route53.py (original)
+++ libcloud/trunk/libcloud/dns/drivers/route53.py Sat Jan  5 23:35:44 2013
@@ -20,11 +20,13 @@ __all__ = [
 import base64
 import hmac
 import datetime
+import uuid
+from libcloud.utils.py3 import httplib
 
 from hashlib import sha1
 from xml.etree import ElementTree as ET
 
-from libcloud.utils.py3 import b
+from libcloud.utils.py3 import b, urlencode
 
 from libcloud.utils.xml import findtext, findall, fixxpath
 from libcloud.dns.types import Provider, RecordType
@@ -42,17 +44,8 @@ API_ROOT = '/%s/' % (API_VERSION)
 NAMESPACE = 'https://%s/doc%s' % (API_HOST, API_ROOT)
 
 
-class Route53Error(LibcloudError):
-    def __init__(self, code, errors):
-        self.code = code
-        self.errors = errors or []
-
-    def __str__(self):
-        return 'Errors: %s' % (', '.join(self.errors))
-
-    def __repr__(self):
-        return('<Route53 response code=%s>' %
-               (self.code, len(self.errors)))
+class InvalidChangeBatch(LibcloudError):
+    pass
 
 
 class Route53DNSResponse(AWSBaseResponse):
@@ -62,28 +55,42 @@ class Route53DNSResponse(AWSBaseResponse
     def success(self):
         return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]
 
-    def error(self):
+    def parse_error(self):
+        context = self.connection.context
         status = int(self.status)
 
-        if status == 403:
+        if status == httplib.FORBIDDEN:
             if not self.body:
                 raise InvalidCredsError(str(self.status) + ': ' + self.error)
             else:
                 raise InvalidCredsError(self.body)
 
-        elif status == 400:
-            context = self.connection.context
-            messages = []
-            if context['InvalidChangeBatch']['Messages']:
-                for message in context['InvalidChangeBatch']['Messages']:
-                    messages.append(message['Message'])
+        try:
+            body = ET.XML(self.body)
+        except Exception:
+            raise MalformedResponseError('Failed to parse XML',
+                                         body=self.body, driver=self.driver)
+
+        errs = findall(element=body, xpath='Error', namespace=NAMESPACE)
+
+        if errs:
+            t, code, message = errs[0].getchildren()
+
+            if code.text == 'NoSuchHostedZone':
+                zone_id = context.get('zone_id', None)
+                raise ZoneDoesNotExistError(value=message.text, driver=self,
+                                            zone_id=zone_id)
+            elif code.text == 'InvalidChangeBatch':
+                raise InvalidChangeBatch(value=message.text)
+            else:
+                return message.text
 
-                raise Route53Error('InvalidChangeBatch message(s): %s ',
-                                   messages)
+        return self.body
 
 
 class Route53Connection(ConnectionUserAndKey):
     host = API_HOST
+    responseCls = Route53DNSResponse
 
     def pre_connect_hook(self, params, headers):
         time_string = datetime.datetime.utcnow() \
@@ -123,36 +130,139 @@ class Route53DNSDriver(DNSDriver):
         RecordType.AAAA: 'AAAA',
         RecordType.CNAME: 'CNAME',
         RecordType.TXT: 'TXT',
-        RecordType.SRV: 'SRV'
+        RecordType.SRV: 'SRV',
+        RecordType.PTR: 'PTR',
+        RecordType.SOA: 'SOA',
+        RecordType.SPF: 'SPF',
+        RecordType.TXT: 'TXT'
     }
 
     def list_zones(self):
-        data = ET.XML(self.connection.request(API_ROOT + 'hostedzone').object)
+        data = self.connection.request(API_ROOT + 'hostedzone').object
         zones = self._to_zones(data=data)
         return zones
 
     def list_records(self, zone):
-        data = ET.XML(self.connection.request(API_ROOT + 'hostedzone/'
-                      + zone.id + '/rrset').object)
+        self.connection.set_context({'zone_id': zone.id})
+        uri = API_ROOT + 'hostedzone/' + zone.id + '/rrset'
+        data = self.connection.request(uri).object
         records = self._to_records(data=data, zone=zone)
         return records
 
     def get_zone(self, zone_id):
-        data = ET.XML(self.connection.request(API_ROOT + 'hostedzone/'
-                      + zone_id).object)
-        zone = self._to_zone(elem=findall(element=data, xpath='HostedZone',
-                                          namespace=NAMESPACE)[0])
-        return zone
+        self.connection.set_context({'zone_id': zone_id})
+        uri = API_ROOT + 'hostedzone/' + zone_id
+        data = self.connection.request(uri).object
+        elem = findall(element=data, xpath='HostedZone',
+                       namespace=NAMESPACE)[0]
+        return self._to_zone(elem)
 
     def get_record(self, zone_id, record_id):
         zone = self.get_zone(zone_id=zone_id)
-        data = ET.XML(self.connection.request(API_ROOT + 'hostedzone/'
-                      + zone_id + '/rrset?maxitems=1&name=' + record_id)
-                      .object)
+        record_type, name = record_id.split(':', 1)
+        self.connection.set_context({'zone_id': zone_id})
+        params = urlencode({'name': name, 'type': record_type})
+        uri = API_ROOT + 'hostedzone/' + zone_id + '/rrset?' + params
+        data = self.connection.request(uri).object
+
+        record = self._to_records(data=data, zone=zone)[0]
+
+        # A cute aspect of the /rrset filters is that they are more pagination
+        # hints than filters!!
+        # So will return a result even if its not what you asked for.
+        record_type_num = self._string_to_record_type(record_type)
+        if record.name != name or record.type != record_type_num:
+            raise RecordDoesNotExistError(value='', driver=self,
+                                          record_id=record_id)
 
-        record = self._to_records(data=data, zone=zone)
         return record
 
+    def create_zone(self, domain, type='master', ttl=None, extra=None):
+        zone = ET.Element('CreateHostedZoneRequest', {'xmlns': NAMESPACE})
+        ET.SubElement(zone, 'Name').text = domain
+        ET.SubElement(zone, 'CallerReference').text = str(uuid.uuid4())
+
+        if extra and 'Comment' in extra:
+            hzg = ET.SubElement(zone, 'HostedZoneConfig')
+            ET.SubElement(hzg, 'Comment').text = extra['Comment']
+
+        uri = API_ROOT + 'hostedzone'
+        data = ET.tostring(zone)
+        rsp = self.connection.request(uri, method='POST', data=data).object
+
+        elem = findall(element=rsp, xpath='HostedZone', namespace=NAMESPACE)[0]
+        return self._to_zone(elem=elem)
+
+    def delete_zone(self, zone, ex_delete_records=False):
+        self.connection.set_context({'zone_id': zone.id})
+
+        if ex_delete_records:
+            self.ex_delete_all_records(zone=zone)
+
+        uri = API_ROOT + 'hostedzone/%s' % (zone.id)
+        response = self.connection.request(uri, method='DELETE')
+        return response.status in [httplib.OK]
+
+    def ex_delete_all_records(self, zone):
+        deletions = []
+        for r in zone.list_records():
+            if r.type in (RecordType.NS, RecordType.SOA):
+                continue
+            deletions.append(('DELETE', r.name, r.type, r.data, r.extra))
+
+        if deletions:
+            self._post_changeset(zone, deletions)
+
+    def create_record(self, name, zone, type, data, extra=None):
+        batch = [('CREATE', name, type, data, extra)]
+        self._post_changeset(zone, batch)
+        id = ':'.join((self.RECORD_TYPE_MAP[type], name))
+        return Record(id=id, name=name, type=type, data=data, zone=zone,
+                      driver=self, extra=extra)
+
+    def update_record(self, record, name, type, data, extra):
+        batch = [
+            ('DELETE', record.name, record.type, record.data, record.extra),
+            ('CREATE', name, type, data, extra)]
+        self._post_changeset(record.zone, batch)
+        id = ':'.join((self.RECORD_TYPE_MAP[type], name))
+        return Record(id=id, name=name, type=type, data=data, zone=record.zone,
+                      driver=self, extra=extra)
+
+    def delete_record(self, record):
+        try:
+            r = record
+            batch = [('DELETE', r.name, r.type, r.data, r.extra)]
+            self._post_changeset(record.zone, batch)
+        except InvalidChangeBatch:
+            raise RecordDoesNotExistError(value='', driver=self,
+                                          record_id=r.id)
+        return True
+
+    def _post_changeset(self, zone, changes_list):
+        attrs = {'xmlns': NAMESPACE}
+        changeset = ET.Element('ChangeResourceRecordSetsRequest', attrs)
+        batch = ET.SubElement(changeset, 'ChangeBatch')
+        changes = ET.SubElement(batch, 'Changes')
+
+        for action, name, type_, data, extra in changes_list:
+            change = ET.SubElement(changes, 'Change')
+            ET.SubElement(change, 'Action').text = action
+
+            rrs = ET.SubElement(change, 'ResourceRecordSet')
+            ET.SubElement(rrs, 'Name').text = name + "." + zone.domain
+            ET.SubElement(rrs, 'Type').text = self.RECORD_TYPE_MAP[type_]
+            ET.SubElement(rrs, 'TTL').text = extra.get('ttl', '0')
+
+            rrecs = ET.SubElement(rrs, 'ResourceRecords')
+            rrec = ET.SubElement(rrecs, 'ResourceRecord')
+            ET.SubElement(rrec, 'Value').text = data
+
+        uri = API_ROOT + 'hostedzone/' + zone.id + '/rrset'
+        data = ET.tostring(changeset)
+        self.connection.set_context({'zone_id': zone.id})
+        rsp = self.connection.request(uri, method='POST', data=data).object
+
     def _to_zones(self, data):
         zones = []
         for element in data.findall(fixxpath(xpath='HostedZones/HostedZone',
@@ -180,9 +290,10 @@ class Route53DNSDriver(DNSDriver):
 
     def _to_records(self, data, zone):
         records = []
-        for elem in data.findall(
+        elems = data.findall(
             fixxpath(xpath='ResourceRecordSets/ResourceRecordSet',
-                     namespace=NAMESPACE)):
+                     namespace=NAMESPACE))
+        for elem in elems:
             records.append(self._to_record(elem, zone))
 
         return records
@@ -190,6 +301,8 @@ class Route53DNSDriver(DNSDriver):
     def _to_record(self, elem, zone):
         name = findtext(element=elem, xpath='Name',
                         namespace=NAMESPACE)
+        name = name[:-len(zone.domain) - 1]
+
         type = self._string_to_record_type(findtext(element=elem, xpath='Type',
                                                     namespace=NAMESPACE))
         ttl = findtext(element=elem, xpath='TTL', namespace=NAMESPACE)
@@ -202,6 +315,8 @@ class Route53DNSDriver(DNSDriver):
                         namespace=NAMESPACE)
 
         extra = {'ttl': ttl}
-        record = Record(id=name, name=name, type=type, data=data, zone=zone,
+
+        id = ':'.join((self.RECORD_TYPE_MAP[type], name))
+        record = Record(id=id, name=name, type=type, data=data, zone=zone,
                         driver=self, extra=extra)
         return record

Modified: libcloud/trunk/libcloud/dns/providers.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/providers.py?rev=1429425&r1=1429424&r2=1429425&view=diff
==============================================================================
--- libcloud/trunk/libcloud/dns/providers.py (original)
+++ libcloud/trunk/libcloud/dns/providers.py Sat Jan  5 23:35:44 2013
@@ -30,6 +30,8 @@ DRIVERS = {
         ('libcloud.dns.drivers.rackspace', 'RackspaceUKDNSDriver'),
     Provider.HOSTVIRTUAL:
         ('libcloud.dns.drivers.hostvirtual', 'HostVirtualDNSDriver'),
+    Provider.ROUTE53:
+        ('libcloud.dns.drivers.route53', 'Route53DNSDriver'),
 }
 
 

Added: libcloud/trunk/libcloud/test/dns/fixtures/route53/create_zone.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/route53/create_zone.xml?rev=1429425&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/route53/create_zone.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/route53/create_zone.xml Sat Jan  5 23:35:44 2013
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CreateHostedZoneResponse xmlns="https://route53.amazonaws.com/doc/2012-02-29/">
+   <HostedZone>
+      <Id>/hostedzone/47234</Id>
+      <Name>t.com</Name>
+      <CallerReference>some unique reference</CallerReference>
+      <Config>
+         <Comment>some comment</Comment>
+      </Config>
+      <ResourceRecordSetCount>0</ResourceRecordSetCount>
+   </HostedZone>
+   <DelegationSet>
+      <NameServers>
+         <NameServer>ns1.example.com</NameServer>
+         <NameServer>ns2.example.com</NameServer>
+         <NameServer>ns3.example.com</NameServer>
+         <NameServer>ns4.example.com</NameServer>
+      </NameServers>
+   </DelegationSet>
+</CreateHostedZoneResponse>
\ No newline at end of file

Added: libcloud/trunk/libcloud/test/dns/fixtures/route53/get_zone.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/route53/get_zone.xml?rev=1429425&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/route53/get_zone.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/route53/get_zone.xml Sat Jan  5 23:35:44 2013
@@ -0,0 +1,21 @@
+
+<?xml version="1.0" encoding="UTF-8"?>
+<GetHostedZoneResponse xmlns="https://route53.amazonaws.com/doc/2012-02-29/">
+   <HostedZone>
+      <Id>/hostedzone/47234</Id>
+      <Name>t.com</Name>
+      <CallerReference>some unique reference</CallerReference>
+      <Config>
+         <Comment>some comment</Comment>
+      </Config>
+      <ResourceRecordSetCount>0</ResourceRecordSetCount>
+   </HostedZone>
+   <DelegationSet>
+      <NameServers>
+         <NameServer>ns1.example.com</NameServer>
+         <NameServer>ns2.example.com</NameServer>
+         <NameServer>ns3.example.com</NameServer>
+         <NameServer>ns4.example.com</NameServer>
+      </NameServers>
+   </DelegationSet>
+</GetHostedZoneResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/route53/invalid_change_batch.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/route53/invalid_change_batch.xml?rev=1429425&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/route53/invalid_change_batch.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/route53/invalid_change_batch.xml Sat Jan  5 23:35:44 2013
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2012-02-29/">
+  <Error>
+    <Type>Sender</Type>
+    <Code>InvalidChangeBatch</Code>
+    <Message>Invalid change</Message>
+  </Error>
+  <RequestId>376c64a6-6194-11e1-847f-ddaa49e4c811</RequestId>
+</ErrorResponse>
\ No newline at end of file

Added: libcloud/trunk/libcloud/test/dns/fixtures/route53/list_records.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/route53/list_records.xml?rev=1429425&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/route53/list_records.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/route53/list_records.xml Sat Jan  5 23:35:44 2013
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ListResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2012-02-29/">
+   <ResourceRecordSets>
+
+      <ResourceRecordSet>
+         <Name>wibble.t.com</Name>
+         <Type>CNAME</Type>
+         <TTL>86400</TTL>
+         <ResourceRecords>
+            <ResourceRecord>
+               <Value>t.com</Value>
+            </ResourceRecord>
+         </ResourceRecords>
+      </ResourceRecordSet>
+
+      <ResourceRecordSet>
+         <Name>www.t.com</Name>
+         <Type>A</Type>
+         <TTL>86400</TTL>
+         <ResourceRecords>
+            <ResourceRecord>
+               <Value>208.111.35.173</Value>
+            </ResourceRecord>
+         </ResourceRecords>
+      </ResourceRecordSet>
+
+      <ResourceRecordSet>
+         <Name>blahblah.t.com</Name>
+         <Type>A</Type>
+         <TTL>86400</TTL>
+         <ResourceRecords>
+            <ResourceRecord>
+               <Value>208.111.35.173</Value>
+            </ResourceRecord>
+         </ResourceRecords>
+      </ResourceRecordSet>
+
+  </ResourceRecordSets>
+</ListResourceRecordSetsResponse>
+

Added: libcloud/trunk/libcloud/test/dns/fixtures/route53/list_zones.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/route53/list_zones.xml?rev=1429425&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/route53/list_zones.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/route53/list_zones.xml Sat Jan  5 23:35:44 2013
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ListHostedZonesResponse xmlns="https://route53.amazonaws.com/doc/2012-02-29/"> 
+   <HostedZones>
+      <HostedZone>
+         <Id>/hostedzone/47234</Id>
+         <Name>t.com</Name>
+         <CallerReference>unique description</CallerReference>
+         <Config>
+            <Comment>some comment</Comment>
+         </Config>
+         <ResourceRecordSetCount>0</ResourceRecordSetCount>
+      </HostedZone>
+
+      <HostedZone>
+         <Id>/hostedzone/48170</Id>
+         <Name>newbug.net</Name>
+         <CallerReference>unique description</CallerReference>
+         <Config>
+            <Comment>some comment</Comment>
+         </Config>
+         <ResourceRecordSetCount>0</ResourceRecordSetCount>
+      </HostedZone>
+
+      <HostedZone>
+         <Id>/hostedzone/48017</Id>
+         <Name>newblah.com</Name>
+         <CallerReference>unique description</CallerReference>
+         <Config>
+            <Comment>some comment</Comment>
+         </Config>
+         <ResourceRecordSetCount>0</ResourceRecordSetCount>
+      </HostedZone>
+
+      <HostedZone>
+         <Id>/hostedzone/47288</Id>
+         <Name>fromapi.com</Name>
+         <CallerReference>unique description</CallerReference>
+         <Config>
+            <Comment>some comment</Comment>
+         </Config>
+         <ResourceRecordSetCount>0</ResourceRecordSetCount>
+      </HostedZone>
+
+      <HostedZone>
+         <Id>/hostedzone/48008</Id>
+         <Name>blahnew.com</Name>
+         <CallerReference>unique description</CallerReference>
+         <Config>
+            <Comment>some comment</Comment>
+         </Config>
+         <ResourceRecordSetCount>0</ResourceRecordSetCount>
+      </HostedZone>
+   </HostedZones>
+</ListHostedZonesResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/route53/record_does_not_exist.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/route53/record_does_not_exist.xml?rev=1429425&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/route53/record_does_not_exist.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/route53/record_does_not_exist.xml Sat Jan  5 23:35:44 2013
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ListResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2012-02-29/">
+   <ResourceRecordSets>
+
+      <ResourceRecordSet>
+         <Name>definitely.not.what.you.askedfor.t.com</Name>
+         <Type>CNAME</Type>
+         <TTL>86400</TTL>
+         <ResourceRecords>
+            <ResourceRecord>
+               <Value>t.com</Value>
+            </ResourceRecord>
+         </ResourceRecords>
+      </ResourceRecordSet>
+
+  </ResourceRecordSets>
+</ListResourceRecordSetsResponse>
\ No newline at end of file

Added: libcloud/trunk/libcloud/test/dns/fixtures/route53/zone_does_not_exist.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/route53/zone_does_not_exist.xml?rev=1429425&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/route53/zone_does_not_exist.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/route53/zone_does_not_exist.xml Sat Jan  5 23:35:44 2013
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2012-02-29/">
+  <Error>
+    <Type>Sender</Type>
+    <Code>NoSuchHostedZone</Code>
+    <Message>No hosted zone found with ID: 47234</Message>
+  </Error>
+  <RequestId>376c64a6-6194-11e1-847f-ddaa49e4c811</RequestId>
+</ErrorResponse>
\ No newline at end of file

Added: libcloud/trunk/libcloud/test/dns/test_route53.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/test_route53.py?rev=1429425&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/test_route53.py (added)
+++ libcloud/trunk/libcloud/test/dns/test_route53.py Sat Jan  5 23:35:44 2013
@@ -0,0 +1,255 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+import sys
+import unittest
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.dns.types import RecordType, ZoneDoesNotExistError
+from libcloud.dns.types import RecordDoesNotExistError
+from libcloud.dns.drivers.route53 import Route53DNSDriver
+from libcloud.test import MockHttp
+from libcloud.test.file_fixtures import DNSFileFixtures
+from libcloud.test.secrets import DNS_PARAMS_ROUTE53
+
+
+class Route53Tests(unittest.TestCase):
+    def setUp(self):
+        Route53DNSDriver.connectionCls.conn_classes = (
+            Route53MockHttp, Route53MockHttp)
+        Route53MockHttp.type = None
+        self.driver = Route53DNSDriver(*DNS_PARAMS_ROUTE53)
+
+    def test_list_record_types(self):
+        record_types = self.driver.list_record_types()
+        self.assertEqual(len(record_types), 10)
+        self.assertTrue(RecordType.A in record_types)
+
+    def test_list_zones(self):
+        zones = self.driver.list_zones()
+        self.assertEqual(len(zones), 5)
+
+        zone = zones[0]
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 't.com')
+
+    def test_list_records(self):
+        zone = self.driver.list_zones()[0]
+        records = self.driver.list_records(zone=zone)
+        self.assertEqual(len(records), 3)
+
+        record = records[1]
+        self.assertEqual(record.name, 'www')
+        self.assertEqual(record.id, 'A:www')
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '208.111.35.173')
+
+    def test_get_zone(self):
+        zone = self.driver.get_zone(zone_id='47234')
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 't.com')
+
+    def test_get_record(self):
+        record = self.driver.get_record(zone_id='47234',
+                                        record_id='CNAME:wibble')
+        self.assertEqual(record.name, 'wibble')
+        self.assertEqual(record.type, RecordType.CNAME)
+        self.assertEqual(record.data, 't.com')
+
+    def test_list_records_zone_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+
+        Route53MockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.list_records(zone=zone)
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, zone.id)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_zone_does_not_exist(self):
+        Route53MockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.get_zone(zone_id='47234')
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, '47234')
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_record_zone_does_not_exist(self):
+        Route53MockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.get_record(zone_id='4444', record_id='28536')
+        except ZoneDoesNotExistError:
+            pass
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_record_record_does_not_exist(self):
+        Route53MockHttp.type = 'RECORD_DOES_NOT_EXIST'
+
+        rid = 'CNAME:doesnotexist.t.com'
+        try:
+            self.driver.get_record(zone_id='47234',
+                                   record_id=rid)
+        except RecordDoesNotExistError:
+            pass
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_create_zone(self):
+        zone = self.driver.create_zone(domain='t.com', type='master',
+                                       ttl=None, extra=None)
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.domain, 't.com')
+
+    def test_create_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.create_record(
+            name='www', zone=zone,
+            type=RecordType.A, data='127.0.0.1',
+            extra={'ttl': 0}
+        )
+
+        self.assertEqual(record.id, 'A:www')
+        self.assertEqual(record.name, 'www')
+        self.assertEqual(record.zone, zone)
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '127.0.0.1')
+
+    def test_update_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[1]
+
+        params = {
+            'record': record,
+            'name': 'www',
+            'type': RecordType.A,
+            'data': '::1',
+            'extra': {'ttle': 0}}
+        updated_record = self.driver.update_record(**params)
+
+        self.assertEqual(record.data, '208.111.35.173')
+
+        self.assertEqual(updated_record.id, 'A:www')
+        self.assertEqual(updated_record.name, 'www')
+        self.assertEqual(updated_record.zone, record.zone)
+        self.assertEqual(updated_record.type, RecordType.A)
+        self.assertEqual(updated_record.data, '::1')
+
+    def test_delete_zone(self):
+        zone = self.driver.list_zones()[0]
+        status = self.driver.delete_zone(zone=zone)
+        self.assertTrue(status)
+
+    def test_delete_zone_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+
+        Route53MockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.delete_zone(zone=zone)
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, zone.id)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_delete_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[0]
+        status = self.driver.delete_record(record=record)
+        self.assertTrue(status)
+
+    def test_delete_record_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[0]
+        Route53MockHttp.type = 'RECORD_DOES_NOT_EXIST'
+        try:
+            self.driver.delete_record(record=record)
+        except RecordDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.record_id, record.id)
+        else:
+            self.fail('Exception was not thrown')
+
+
+class Route53MockHttp(MockHttp):
+    fixtures = DNSFileFixtures('route53')
+
+    def _2012_02_29_hostedzone_47234(self, method, url, body, headers):
+        body = self.fixtures.load('get_zone.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _2012_02_29_hostedzone(self, method, url, body, headers):
+        #print method, url, body, headers
+        if method == "POST":
+            body = self.fixtures.load("create_zone.xml")
+            return (httplib.CREATED, body, {}, httplib.responses[httplib.OK])
+        body = self.fixtures.load('list_zones.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _2012_02_29_hostedzone_47234_rrset(self, method, url, body, headers):
+        body = self.fixtures.load('list_records.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _2012_02_29_hostedzone_47234_rrset_ZONE_DOES_NOT_EXIST(self, method,
+                                              url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.xml')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _2012_02_29_hostedzone_4444_ZONE_DOES_NOT_EXIST(self, method,
+                                              url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.xml')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _2012_02_29_hostedzone_47234_ZONE_DOES_NOT_EXIST(self, method,
+                                              url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.xml')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _2012_02_29_hostedzone_47234_rrset_ZONE_DOES_NOT_EXIST(self, method,
+                                              url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.xml')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _2012_02_29_hostedzone_47234_rrset_RECORD_DOES_NOT_EXIST(self, method,
+                                              url, body, headers):
+        if method == "POST":
+            body = self.fixtures.load('invalid_change_batch.xml')
+            return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.BAD_REQUEST])
+        body = self.fixtures.load('record_does_not_exist.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _2012_02_29_hostedzone_47234_RECORD_DOES_NOT_EXIST(self, method,
+                                              url, body, headers):
+        body = self.fixtures.load('get_zone.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

Modified: libcloud/trunk/libcloud/test/secrets.py-dist
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/secrets.py-dist?rev=1429425&r1=1429424&r2=1429425&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/secrets.py-dist (original)
+++ libcloud/trunk/libcloud/test/secrets.py-dist Sat Jan  5 23:35:44 2013
@@ -52,3 +52,4 @@ DNS_PARAMS_LINODE = ('user', 'key')
 DNS_PARAMS_ZERIGO = ('email', 'api token')
 DNS_PARAMS_RACKSPACE = ('user', 'key')
 DNS_PARAMS_HOSTVIRTUAL = ('key',)
+DNS_PARAMS_ROUTE53 = ('access_id', 'secret')