Update Route53 driver so update_record method works correctly for records with multiple values.
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/692c5283 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/692c5283 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/692c5283 Branch: refs/heads/trunk Commit: 692c5283459e7f6a6ccf064292fa4c0d1a7e9508 Parents: ae7d6ae Author: Tomaz Muraus <[email protected]> Authored: Fri Mar 21 14:31:18 2014 +0100 Committer: Tomaz Muraus <[email protected]> Committed: Fri Mar 21 14:34:45 2014 +0100 ---------------------------------------------------------------------- libcloud/dns/drivers/route53.py | 128 ++++++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/692c5283/libcloud/dns/drivers/route53.py ---------------------------------------------------------------------- diff --git a/libcloud/dns/drivers/route53.py b/libcloud/dns/drivers/route53.py index 09dc250..5cc8664 100644 --- a/libcloud/dns/drivers/route53.py +++ b/libcloud/dns/drivers/route53.py @@ -21,6 +21,7 @@ import base64 import hmac import datetime import uuid +import copy from libcloud.utils.py3 import httplib from hashlib import sha1 @@ -190,9 +191,71 @@ class Route53DNSDriver(DNSDriver): return Record(id=id, name=name, type=type, data=data, zone=zone, driver=self, extra=extra) + def _update_single_value_record(self, record, name=None, type=None, + data=None, extra=None): + batch = [ + ('DELETE', record.name, record.type, record.data, record.extra), + ('CREATE', name, type, data, extra) + ] + + return self._post_changeset(record.zone, batch) + + def _update_multi_value_record(self, record, name=None, type=None, + data=None, extra=None): + other_records = record.extra.get('_other_records', []) + + attrs = {'xmlns': NAMESPACE} + changeset = ET.Element('ChangeResourceRecordSetsRequest', attrs) + batch = ET.SubElement(changeset, 'ChangeBatch') + changes = ET.SubElement(batch, 'Changes') + + # Delete existing records + change = ET.SubElement(changes, 'Change') + ET.SubElement(change, 'Action').text = 'DELETE' + + rrs = ET.SubElement(change, 'ResourceRecordSet') + ET.SubElement(rrs, 'Name').text = record.name + '.' + \ + record.zone.domain + ET.SubElement(rrs, 'Type').text = self.RECORD_TYPE_MAP[record.type] + ET.SubElement(rrs, 'TTL').text = str(record.extra.get('ttl', '0')) + + rrecs = ET.SubElement(rrs, 'ResourceRecords') + + rrec = ET.SubElement(rrecs, 'ResourceRecord') + ET.SubElement(rrec, 'Value').text = record.data + + for other_record in other_records: + rrec = ET.SubElement(rrecs, 'ResourceRecord') + ET.SubElement(rrec, 'Value').text = other_record['data'] + + # Re-create new (updated) records. Since we are updating a multi value + # record, only a single record is updated and others are left as is. + change = ET.SubElement(changes, 'Change') + ET.SubElement(change, 'Action').text = 'CREATE' + + rrs = ET.SubElement(change, 'ResourceRecordSet') + ET.SubElement(rrs, 'Name').text = name + '.' + record.zone.domain + ET.SubElement(rrs, 'Type').text = self.RECORD_TYPE_MAP[type] + ET.SubElement(rrs, 'TTL').text = str(extra.get('ttl', '0')) + + rrecs = ET.SubElement(rrs, 'ResourceRecords') + + rrec = ET.SubElement(rrecs, 'ResourceRecord') + ET.SubElement(rrec, 'Value').text = data + + for other_record in other_records: + rrec = ET.SubElement(rrecs, 'ResourceRecord') + ET.SubElement(rrec, 'Value').text = other_record['data'] + + uri = API_ROOT + 'hostedzone/' + record.zone.id + '/rrset' + data = ET.tostring(changeset) + self.connection.set_context({'zone_id': record.zone.id}) + response = self.connection.request(uri, method='POST', data=data) + + return response.status == httplib.OK + def update_record(self, record, name=None, type=None, data=None, extra=None): - if not name: name = record.name @@ -202,10 +265,20 @@ class Route53DNSDriver(DNSDriver): if not extra: extra = record.extra - batch = [ - ('DELETE', record.name, record.type, record.data, record.extra), - ('CREATE', name, type, data, extra)] - self._post_changeset(record.zone, batch) + # Multiple value records need to be handled specially - we need to + # pass values for other records as well + multiple_value_record = record.extra.get('_multi_value', False) + other_records = record.extra.get('_other_records', []) + + if multiple_value_record and other_records: + self._update_multi_value_record(record=record, name=name, + type=type, data=data, + extra=extra) + else: + self._update_single_value_record(record=record, name=name, + type=type, data=data, + extra=extra) + 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) @@ -247,7 +320,7 @@ class Route53DNSDriver(DNSDriver): ET.SubElement(change, 'Action').text = action rrs = ET.SubElement(change, 'ResourceRecordSet') - ET.SubElement(rrs, 'Name').text = name + "." + zone.domain + ET.SubElement(rrs, 'Name').text = name + '.' + zone.domain ET.SubElement(rrs, 'Type').text = self.RECORD_TYPE_MAP[type_] ET.SubElement(rrs, 'TTL').text = str(extra.get('ttl', '0')) @@ -258,7 +331,9 @@ class Route53DNSDriver(DNSDriver): uri = API_ROOT + 'hostedzone/' + zone.id + '/rrset' data = ET.tostring(changeset) self.connection.set_context({'zone_id': zone.id}) - self.connection.request(uri, method='POST', data=data) + response = self.connection.request(uri, method='POST', data=data) + + return response.status == httplib.OK def _to_zones(self, data): zones = [] @@ -294,8 +369,45 @@ class Route53DNSDriver(DNSDriver): record_set = elem.findall(fixxpath( xpath='ResourceRecords/ResourceRecord', namespace=NAMESPACE)) + record_count = len(record_set) + multiple_value_record = (record_count > 1) + + record_set_records = [] + for index, record in enumerate(record_set): - records.append(self._to_record(elem, zone, index)) + # Need to special handling for records with multiple values for + # update to work correctly + record = self._to_record(elem=elem, zone=zone, index=index) + record.extra['_multi_value'] = multiple_value_record + + if multiple_value_record: + record.extra['_other_records'] = [] + + record_set_records.append(record) + + # Store reference to other records so update works correctly + if multiple_value_record: + for index in range(0, len(record_set_records)): + record = record_set_records[index] + + for other_index, other_record in \ + enumerate(record_set_records): + if index == other_index: + # Skip current record + continue + + extra = copy.deepcopy(other_record.extra) + extra.pop('_multi_value') + extra.pop('_other_records') + + item = {'name': other_record.name, + 'data': other_record.data, + 'type': other_record.type, + 'extra': extra} + record.extra['_other_records'].append(item) + + records.extend(record_set_records) + return records def _to_record(self, elem, zone, index=0):
