From b2bb5dcbdcbaa3222619b92cabe061aa16d4bca1 Mon Sep 17 00:00:00 2001 From: Vlad Glagolev Date: Wed, 15 Feb 2017 13:16:20 -0500 Subject: [PATCH 1/4] Add ability to delete multi-value records --- libcloud/dns/drivers/route53.py | 54 +++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/libcloud/dns/drivers/route53.py b/libcloud/dns/drivers/route53.py index 040cdbccc7..72709935e7 100644 --- a/libcloud/dns/drivers/route53.py +++ b/libcloud/dns/drivers/route53.py @@ -219,14 +219,64 @@ def update_record(self, record, name=None, type=None, data=None, driver=self, ttl=extra.get('ttl', None), extra=extra) def delete_record(self, record): + # 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) + + if multiple_value_record: + self._delete_multi_value_record(record) + else: + self._delete_single_value_record(record) + + return True + + def _delete_single_value_record(self, record): try: r = record batch = [('DELETE', r.name, r.type, r.data, r.extra)] - self._post_changeset(record.zone, batch) + + return self._post_changeset(record.zone, batch) except InvalidChangeBatch: raise RecordDoesNotExistError(value='', driver=self, record_id=r.id) - return True + + def _delete_multi_value_record(self, record): + 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') + + change = ET.SubElement(changes, 'Change') + ET.SubElement(change, 'Action').text = 'DELETE' + + rrs = ET.SubElement(change, 'ResourceRecordSet') + + if record.name: + record_name = record.name + '.' + record.zone.domain + else: + record_name = record.zone.domain + + ET.SubElement(rrs, 'Name').text = record_name + 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'] + + 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 ex_create_multi_value_record(self, name, zone, type, data, extra=None): """ From 1bf84bea252f2b79e91c3d930685a9d123265993 Mon Sep 17 00:00:00 2001 From: Vlad Glagolev Date: Wed, 15 Feb 2017 13:43:04 -0500 Subject: [PATCH 2/4] Keep old values for the record as-is --- libcloud/dns/drivers/route53.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/libcloud/dns/drivers/route53.py b/libcloud/dns/drivers/route53.py index 72709935e7..98e33d13f5 100644 --- a/libcloud/dns/drivers/route53.py +++ b/libcloud/dns/drivers/route53.py @@ -267,6 +267,23 @@ def _delete_multi_value_record(self, record): 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 old records. Since we are deleting a multi value record, + # only a single record is deleted 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 = record_name + 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') + for other_record in other_records: rrec = ET.SubElement(rrecs, 'ResourceRecord') ET.SubElement(rrec, 'Value').text = other_record['data'] From b30ce7e332fa5f14ce49a534ce3b5d21bdd8c729 Mon Sep 17 00:00:00 2001 From: Vlad Glagolev Date: Wed, 15 Feb 2017 16:49:35 -0500 Subject: [PATCH 3/4] Move private methods to the bottom --- libcloud/dns/drivers/route53.py | 130 ++++++++++++++++---------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/libcloud/dns/drivers/route53.py b/libcloud/dns/drivers/route53.py index 98e33d13f5..bba0a033a7 100644 --- a/libcloud/dns/drivers/route53.py +++ b/libcloud/dns/drivers/route53.py @@ -230,71 +230,6 @@ def delete_record(self, record): return True - def _delete_single_value_record(self, record): - try: - r = record - batch = [('DELETE', r.name, r.type, r.data, r.extra)] - - return self._post_changeset(record.zone, batch) - except InvalidChangeBatch: - raise RecordDoesNotExistError(value='', driver=self, - record_id=r.id) - - def _delete_multi_value_record(self, record): - 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') - - change = ET.SubElement(changes, 'Change') - ET.SubElement(change, 'Action').text = 'DELETE' - - rrs = ET.SubElement(change, 'ResourceRecordSet') - - if record.name: - record_name = record.name + '.' + record.zone.domain - else: - record_name = record.zone.domain - - ET.SubElement(rrs, 'Name').text = record_name - 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 old records. Since we are deleting a multi value record, - # only a single record is deleted 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 = record_name - 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') - - 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 ex_create_multi_value_record(self, name, zone, type, data, extra=None): """ Create a record with multiple values with a single call. @@ -432,6 +367,71 @@ def _update_multi_value_record(self, record, name=None, type=None, return response.status == httplib.OK + def _delete_single_value_record(self, record): + try: + r = record + batch = [('DELETE', r.name, r.type, r.data, r.extra)] + + return self._post_changeset(record.zone, batch) + except InvalidChangeBatch: + raise RecordDoesNotExistError(value='', driver=self, + record_id=r.id) + + def _delete_multi_value_record(self, record): + 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') + + change = ET.SubElement(changes, 'Change') + ET.SubElement(change, 'Action').text = 'DELETE' + + rrs = ET.SubElement(change, 'ResourceRecordSet') + + if record.name: + record_name = record.name + '.' + record.zone.domain + else: + record_name = record.zone.domain + + ET.SubElement(rrs, 'Name').text = record_name + 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 old records. Since we are deleting a multi value record, + # only a single record is deleted 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 = record_name + 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') + + 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 _post_changeset(self, zone, changes_list): attrs = {'xmlns': NAMESPACE} changeset = ET.Element('ChangeResourceRecordSetsRequest', attrs) From 16ba682e755a0d732c1759144715df7021127054 Mon Sep 17 00:00:00 2001 From: Vlad Glagolev Date: Sat, 7 Apr 2018 12:24:48 -0400 Subject: [PATCH 4/4] Duplicate delete test for a multi-value record --- libcloud/test/dns/test_route53.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libcloud/test/dns/test_route53.py b/libcloud/test/dns/test_route53.py index 1692f222be..3391abffa9 100644 --- a/libcloud/test/dns/test_route53.py +++ b/libcloud/test/dns/test_route53.py @@ -232,6 +232,12 @@ def test_delete_record(self): status = self.driver.delete_record(record=record) self.assertTrue(status) + def test_delete_multi_value_record(self): + zone = self.driver.list_zones()[0] + record = self.driver.list_records(zone=zone)[3] + 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]