Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions SoftLayer/CLI/hardware/dns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""Sync DNS records."""
# :license: MIT, see LICENSE for more details.

import click

import SoftLayer
from SoftLayer.CLI import environment
from SoftLayer.CLI import exceptions
from SoftLayer.CLI import formatting
from SoftLayer.CLI import helpers


@click.command(epilog="""If you don't specify any
arguments, it will attempt to update both the A and PTR records. If you don't
want to update both records, you may use the -a or --ptr arguments to limit
the records updated.""")
@click.argument('identifier')
@click.option('--a-record', '-a',
is_flag=True,
help="Sync the A record for the host")
@click.option('--aaaa-record',
is_flag=True,
help="Sync the AAAA record for the host")
@click.option('--ptr', is_flag=True, help="Sync the PTR record for the host")
@click.option('--ttl',
default=7200,
show_default=True,
type=click.INT,
help="Sets the TTL for the A and/or PTR records")
@environment.pass_env
def cli(env, identifier, a_record, aaaa_record, ptr, ttl):
"""Sync DNS records."""

items = ['id',
'globalIdentifier',
'fullyQualifiedDomainName',
'hostname',
'domain',
'primaryBackendIpAddress',
'primaryIpAddress',
'''primaryNetworkComponent[
id, primaryIpAddress,
primaryVersion6IpAddressRecord[ipAddress]
]''']
mask = "mask[%s]" % ','.join(items)
dns = SoftLayer.DNSManager(env.client)
server = SoftLayer.HardwareManager(env.client)

hw_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS')
instance = server.get_hardware(hw_id, mask=mask)
zone_id = helpers.resolve_id(dns.resolve_ids,
instance['domain'],
name='zone')

def sync_a_record():
"""Sync A record."""
records = dns.get_records(zone_id,
host=instance['hostname'],
record_type='a')
if not records:
# don't have a record, lets add one to the base zone
dns.create_record(zone['id'],
instance['hostname'],
'a',
instance['primaryIpAddress'],
ttl=ttl)
else:
if len(records) != 1:
raise exceptions.CLIAbort("Aborting A record sync, found "
"%d A record exists!" % len(records))
rec = records[0]
rec['data'] = instance['primaryIpAddress']
rec['ttl'] = ttl
dns.edit_record(rec)

def sync_aaaa_record():
"""Sync AAAA record."""
records = dns.get_records(zone_id,
host=instance['hostname'],
record_type='aaaa')
try:
# done this way to stay within 80 character lines
component = instance['primaryNetworkComponent']
record = component['primaryVersion6IpAddressRecord']
ip_address = record['ipAddress']
except KeyError:
raise exceptions.CLIAbort("%s does not have an ipv6 address"
% instance['fullyQualifiedDomainName'])

if not records:
# don't have a record, lets add one to the base zone
dns.create_record(zone['id'],
instance['hostname'],
'aaaa',
ip_address,
ttl=ttl)
else:
if len(records) != 1:
raise exceptions.CLIAbort("Aborting A record sync, found "
"%d A record exists!" % len(records))
rec = records[0]
rec['data'] = ip_address
rec['ttl'] = ttl
dns.edit_record(rec)

def sync_ptr_record():
"""Sync PTR record."""
host_rec = instance['primaryIpAddress'].split('.')[-1]
ptr_domains = (env.client['Hardware_Server']
.getReverseDomainRecords(id=instance['id'])[0])
edit_ptr = None
for ptr in ptr_domains['resourceRecords']:
if ptr['host'] == host_rec:
ptr['ttl'] = ttl
edit_ptr = ptr
break

if edit_ptr:
edit_ptr['data'] = instance['fullyQualifiedDomainName']
dns.edit_record(edit_ptr)
else:
dns.create_record(ptr_domains['id'],
host_rec,
'ptr',
instance['fullyQualifiedDomainName'],
ttl=ttl)

if not instance['primaryIpAddress']:
raise exceptions.CLIAbort('No primary IP address associated with '
'this VS')

zone = dns.get_zone(zone_id)

go_for_it = env.skip_confirmations or formatting.confirm(
"Attempt to update DNS records for %s"
% instance['fullyQualifiedDomainName'])

if not go_for_it:
raise exceptions.CLIAbort("Aborting DNS sync")

both = False
if not ptr and not a_record and not aaaa_record:
both = True

if both or a_record:
sync_a_record()

if both or ptr:
sync_ptr_record()

if aaaa_record:
sync_aaaa_record()
1 change: 1 addition & 0 deletions SoftLayer/CLI/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'),
('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'),
('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'),
('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'),

('securitygroup', 'SoftLayer.CLI.securitygroup'),
('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'),
Expand Down
1 change: 1 addition & 0 deletions SoftLayer/managers/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ def edit_record(self, record):
:param dict record: the record to update

"""
record.pop('isGatewayAddress', None)
self.record.editObject(record, id=record['id'])

def dump_zone(self, zone_id):
Expand Down
187 changes: 187 additions & 0 deletions tests/CLI/modules/server_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,3 +638,190 @@ def test_bandwidth_hw_quite(self):
self.assertEqual(output_summary[1]['Max Date'], date)
self.assertEqual(output_summary[2]['Max GB'], 0.1172)
self.assertEqual(output_summary[3]['Sum GB'], 0.0009)

@mock.patch('SoftLayer.CLI.formatting.confirm')
def test_dns_sync_both(self, confirm_mock):
confirm_mock.return_value = True
getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server',
'getReverseDomainRecords')
getReverseDomainRecords.return_value = [{
'networkAddress': '172.16.1.100',
'name': '2.240.16.172.in-addr.arpa',
'resourceRecords': [{'data': 'test.softlayer.com.',
'id': 100,
'host': '12'}],
'updateDate': '2013-09-11T14:36:57-07:00',
'serial': 1234665663,
'id': 123456,
}]
getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
'getResourceRecords')
getResourceRecords.return_value = []
createAargs = ({
'type': 'a',
'host': 'hardware-test1',
'domainId': 98765,
'data': '172.16.1.100',
'ttl': 7200
},)
createPTRargs = ({
'type': 'ptr',
'host': '100',
'domainId': 123456,
'data': 'hardware-test1.test.sftlyr.ws',
'ttl': 7200
},)

result = self.run_command(['hw', 'dns-sync', '1000'])

self.assert_no_fail(result)
self.assert_called_with('SoftLayer_Dns_Domain', 'getResourceRecords')
self.assert_called_with('SoftLayer_Hardware_Server',
'getReverseDomainRecords')
self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
'createObject',
args=createAargs)
self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
'createObject',
args=createPTRargs)

@mock.patch('SoftLayer.CLI.formatting.confirm')
def test_dns_sync_v6(self, confirm_mock):
confirm_mock.return_value = True
getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
'getResourceRecords')
getResourceRecords.return_value = []
server = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
test_server = {
'id': 1000,
'hostname': 'hardware-test1',
'domain': 'sftlyr.ws',
'primaryIpAddress': '172.16.1.100',
'fullyQualifiedDomainName': 'hw-test1.sftlyr.ws',
"primaryNetworkComponent": {}
}
server.return_value = test_server

result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000'])

self.assertEqual(result.exit_code, 2)
self.assertIsInstance(result.exception, exceptions.CLIAbort)

test_server['primaryNetworkComponent'] = {
'primaryVersion6IpAddressRecord': {
'ipAddress': '2607:f0d0:1b01:0023:0000:0000:0000:0004'
}
}
createV6args = ({
'type': 'aaaa',
'host': 'hardware-test1',
'domainId': 98765,
'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004',
'ttl': 7200
},)
server.return_value = test_server
result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000'])
self.assert_no_fail(result)
self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
'createObject',
args=createV6args)

v6Record = {
'id': 1,
'ttl': 7200,
'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004',
'host': 'hardware-test1',
'type': 'aaaa'
}

getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
'getResourceRecords')
getResourceRecords.return_value = [v6Record]
editArgs = (v6Record,)
result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000'])
self.assert_no_fail(result)
self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
'editObject',
args=editArgs)

getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
'getResourceRecords')
getResourceRecords.return_value = [v6Record, v6Record]
result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000'])
self.assertEqual(result.exit_code, 2)
self.assertIsInstance(result.exception, exceptions.CLIAbort)

@mock.patch('SoftLayer.CLI.formatting.confirm')
def test_dns_sync_edit_a(self, confirm_mock):
confirm_mock.return_value = True
getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
'getResourceRecords')
getResourceRecords.return_value = [
{'id': 1, 'ttl': 7200, 'data': '1.1.1.1',
'host': 'hardware-test1', 'type': 'a'}
]
editArgs = (
{'type': 'a', 'host': 'hardware-test1', 'data': '172.16.1.100',
'id': 1, 'ttl': 7200},
)
result = self.run_command(['hw', 'dns-sync', '-a', '1000'])
self.assert_no_fail(result)
self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
'editObject',
args=editArgs)

getResourceRecords = self.set_mock('SoftLayer_Dns_Domain',
'getResourceRecords')
getResourceRecords.return_value = [
{'id': 1, 'ttl': 7200, 'data': '1.1.1.1',
'host': 'hardware-test1', 'type': 'a'},
{'id': 2, 'ttl': 7200, 'data': '1.1.1.1',
'host': 'hardware-test1', 'type': 'a'}
]
result = self.run_command(['hw', 'dns-sync', '-a', '1000'])
self.assertEqual(result.exit_code, 2)
self.assertIsInstance(result.exception, exceptions.CLIAbort)

@mock.patch('SoftLayer.CLI.formatting.confirm')
def test_dns_sync_edit_ptr(self, confirm_mock):
confirm_mock.return_value = True
getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server',
'getReverseDomainRecords')
getReverseDomainRecords.return_value = [{
'networkAddress': '172.16.1.100',
'name': '2.240.16.172.in-addr.arpa',
'resourceRecords': [{'data': 'test.softlayer.com.',
'id': 123,
'host': '100'}],
'updateDate': '2013-09-11T14:36:57-07:00',
'serial': 1234665663,
'id': 123456,
}]
editArgs = ({'host': '100', 'data': 'hardware-test1.test.sftlyr.ws',
'id': 123, 'ttl': 7200},)
result = self.run_command(['hw', 'dns-sync', '--ptr', '1000'])
self.assert_no_fail(result)
self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord',
'editObject',
args=editArgs)

@mock.patch('SoftLayer.CLI.formatting.confirm')
def test_dns_sync_misc_exception(self, confirm_mock):
confirm_mock.return_value = False
result = self.run_command(['hw', 'dns-sync', '-a', '1000'])
self.assertEqual(result.exit_code, 2)
self.assertIsInstance(result.exception, exceptions.CLIAbort)

guest = self.set_mock('SoftLayer_Hardware_Server', 'getObject')
test_guest = {
'id': 1000,
'primaryIpAddress': '',
'hostname': 'hardware-test1',
'domain': 'sftlyr.ws',
'fullyQualifiedDomainName': 'hardware-test1.sftlyr.ws',
"primaryNetworkComponent": {}
}
guest.return_value = test_guest
result = self.run_command(['hw', 'dns-sync', '-a', '1000'])
self.assertEqual(result.exit_code, 2)
self.assertIsInstance(result.exception, exceptions.CLIAbort)