mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Merge branch 'master' into dynamic-cname
This commit is contained in:
@@ -5,19 +5,23 @@
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
unicode_literals
|
||||
|
||||
from octodns.record import Create, Delete, Record
|
||||
from octodns.record import Create, Update, Delete, Record
|
||||
from octodns.provider.azuredns import _AzureRecord, AzureProvider, \
|
||||
_check_endswith_dot, _parse_azure_type, _check_for_alias
|
||||
_check_endswith_dot, _parse_azure_type, _traffic_manager_suffix, \
|
||||
_get_monitor, _profile_is_match, AzureException
|
||||
from octodns.zone import Zone
|
||||
from octodns.provider.base import Plan
|
||||
|
||||
from azure.mgmt.dns.models import ARecord, AaaaRecord, CaaRecord, \
|
||||
CnameRecord, MxRecord, SrvRecord, NsRecord, PtrRecord, TxtRecord, \
|
||||
RecordSet, SoaRecord, SubResource, Zone as AzureZone
|
||||
from azure.mgmt.trafficmanager.models import Profile, DnsConfig, \
|
||||
MonitorConfig, Endpoint, MonitorConfigCustomHeadersItem
|
||||
from msrestazure.azure_exceptions import CloudError
|
||||
|
||||
from six import text_type
|
||||
from unittest import TestCase
|
||||
from mock import Mock, patch
|
||||
from mock import Mock, patch, call
|
||||
|
||||
|
||||
zone = Zone(name='unit.tests.', sub_zones=[])
|
||||
@@ -343,6 +347,43 @@ class Test_AzureRecord(TestCase):
|
||||
assert(azure_records[i]._equals(octo))
|
||||
|
||||
|
||||
class Test_DynamicAzureRecord(TestCase):
|
||||
def test_azure_record(self):
|
||||
tm_profile = Profile()
|
||||
data = {
|
||||
'ttl': 60,
|
||||
'type': 'CNAME',
|
||||
'value': 'default.unit.tests.',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [
|
||||
{'value': 'one.unit.tests.', 'weight': 1}
|
||||
],
|
||||
'fallback': 'two',
|
||||
},
|
||||
'two': {
|
||||
'values': [
|
||||
{'value': 'two.unit.tests.', 'weight': 1}
|
||||
],
|
||||
},
|
||||
},
|
||||
'rules': [
|
||||
{'geos': ['AF'], 'pool': 'one'},
|
||||
{'pool': 'two'},
|
||||
],
|
||||
}
|
||||
}
|
||||
octo_record = Record.new(zone, 'foo', data)
|
||||
azure_record = _AzureRecord('TestAzure', octo_record,
|
||||
traffic_manager=tm_profile)
|
||||
self.assertEqual(azure_record.zone_name, zone.name[:-1])
|
||||
self.assertEqual(azure_record.relative_record_set_name, 'foo')
|
||||
self.assertEqual(azure_record.record_type, 'CNAME')
|
||||
self.assertEqual(azure_record.params['ttl'], 60)
|
||||
self.assertEqual(azure_record.params['target_resource'], tm_profile)
|
||||
|
||||
|
||||
class Test_ParseAzureType(TestCase):
|
||||
def test_parse_azure_type(self):
|
||||
for expected, test in [['A', 'Microsoft.Network/dnszones/A'],
|
||||
@@ -361,40 +402,330 @@ class Test_CheckEndswithDot(TestCase):
|
||||
self.assertEquals(expected, _check_endswith_dot(test))
|
||||
|
||||
|
||||
class Test_CheckAzureAlias(TestCase):
|
||||
def test_check_for_alias(self):
|
||||
alias_record = type('C', (object,), {})
|
||||
alias_record.target_resource = type('C', (object,), {})
|
||||
alias_record.target_resource.id = "/subscriptions/x/resourceGroups/y/z"
|
||||
alias_record.a_records = None
|
||||
alias_record.cname_record = None
|
||||
class Test_TrafficManagerSuffix(TestCase):
|
||||
def test_traffic_manager_suffix(self):
|
||||
test = Record.new(zone, 'foo', data={
|
||||
'ttl': 60, 'type': 'CNAME', 'value': 'default.unit.tests.',
|
||||
})
|
||||
self.assertEqual(_traffic_manager_suffix(test), 'foo-unit-tests')
|
||||
|
||||
self.assertEquals(_check_for_alias(alias_record), True)
|
||||
|
||||
class Test_GetMonitor(TestCase):
|
||||
def test_get_monitor(self):
|
||||
record = Record.new(zone, 'foo', data={
|
||||
'type': 'CNAME', 'ttl': 60, 'value': 'default.unit.tests.',
|
||||
'octodns': {
|
||||
'healthcheck': {
|
||||
'path': '/_ping',
|
||||
'port': 4443,
|
||||
'protocol': 'HTTPS',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
monitor = _get_monitor(record)
|
||||
self.assertEqual(monitor.protocol, 'HTTPS')
|
||||
self.assertEqual(monitor.port, 4443)
|
||||
self.assertEqual(monitor.path, '/_ping')
|
||||
headers = monitor.custom_headers
|
||||
self.assertIsInstance(headers, list)
|
||||
self.assertEquals(len(headers), 1)
|
||||
headers = headers[0]
|
||||
self.assertEqual(headers.name, 'Host')
|
||||
self.assertEqual(headers.value, record.healthcheck_host)
|
||||
|
||||
# test TCP monitor
|
||||
record._octodns['healthcheck']['protocol'] = 'TCP'
|
||||
monitor = _get_monitor(record)
|
||||
self.assertEqual(monitor.protocol, 'TCP')
|
||||
self.assertIsNone(monitor.custom_headers)
|
||||
|
||||
|
||||
class Test_ProfileIsMatch(TestCase):
|
||||
def test_profile_is_match(self):
|
||||
is_match = _profile_is_match
|
||||
|
||||
self.assertFalse(is_match(None, Profile()))
|
||||
|
||||
# Profile object builder with default property values that can be
|
||||
# overridden for testing below
|
||||
def profile(
|
||||
name = 'foo-unit-tests',
|
||||
ttl = 60,
|
||||
method = 'Geographic',
|
||||
monitor_proto = 'HTTPS',
|
||||
monitor_port = 4443,
|
||||
monitor_path = '/_ping',
|
||||
endpoints = 1,
|
||||
endpoint_name = 'name',
|
||||
endpoint_type = 'profile/nestedEndpoints',
|
||||
target = 'target.unit.tests',
|
||||
target_id = 'resource/id',
|
||||
geos = ['GEO-AF'],
|
||||
weight = 1,
|
||||
priority = 1,
|
||||
):
|
||||
dns = DnsConfig(ttl=ttl)
|
||||
return Profile(
|
||||
name=name, traffic_routing_method=method, dns_config=dns,
|
||||
monitor_config=MonitorConfig(
|
||||
protocol=monitor_proto,
|
||||
port=monitor_port,
|
||||
path=monitor_path,
|
||||
),
|
||||
endpoints=[Endpoint(
|
||||
name=endpoint_name,
|
||||
type=endpoint_type,
|
||||
target=target,
|
||||
target_resource_id=target_id,
|
||||
geo_mapping=geos,
|
||||
weight=weight,
|
||||
priority=priority,
|
||||
)] + [Endpoint()] * (endpoints - 1),
|
||||
)
|
||||
|
||||
self.assertTrue(is_match(profile(), profile()))
|
||||
|
||||
self.assertFalse(is_match(profile(), profile(name='two')))
|
||||
self.assertFalse(is_match(profile(), profile(endpoints=2)))
|
||||
self.assertFalse(is_match(profile(), profile(monitor_proto='HTTP')))
|
||||
self.assertFalse(is_match(profile(), profile(endpoint_name='a')))
|
||||
self.assertFalse(is_match(profile(), profile(endpoint_type='b')))
|
||||
self.assertFalse(
|
||||
is_match(profile(endpoint_type='b'), profile(endpoint_type='b'))
|
||||
)
|
||||
self.assertFalse(is_match(profile(), profile(target_id='rsrc/id2')))
|
||||
self.assertFalse(is_match(profile(), profile(geos=['IN'])))
|
||||
|
||||
def wprofile(**kwargs):
|
||||
kwargs['method'] = 'Weighted'
|
||||
kwargs['endpoint_type'] = 'profile/externalEndpoints'
|
||||
return profile(**kwargs)
|
||||
|
||||
self.assertFalse(is_match(wprofile(), wprofile(target='bar.unit')))
|
||||
self.assertFalse(is_match(wprofile(), wprofile(weight=3)))
|
||||
|
||||
|
||||
class TestAzureDnsProvider(TestCase):
|
||||
def _provider(self):
|
||||
return self._get_provider('mock_spc', 'mock_dns_client')
|
||||
|
||||
@patch('octodns.provider.azuredns.TrafficManagerManagementClient')
|
||||
@patch('octodns.provider.azuredns.DnsManagementClient')
|
||||
@patch('octodns.provider.azuredns.ClientSecretCredential')
|
||||
def _get_provider(self, mock_spc, mock_dns_client):
|
||||
@patch('octodns.provider.azuredns.ServicePrincipalCredentials')
|
||||
def _get_provider(self, mock_spc, mock_css, mock_dns_client,
|
||||
mock_tm_client):
|
||||
'''Returns a mock AzureProvider object to use in testing.
|
||||
|
||||
:param mock_spc: placeholder
|
||||
:type mock_spc: str
|
||||
:param mock_dns_client: placeholder
|
||||
:type mock_dns_client: str
|
||||
:param mock_tm_client: placeholder
|
||||
:type mock_tm_client: str
|
||||
|
||||
:type return: AzureProvider
|
||||
'''
|
||||
provider = AzureProvider('mock_id', 'mock_client', 'mock_key',
|
||||
'mock_directory', 'mock_sub', 'mock_rg'
|
||||
)
|
||||
|
||||
# Fetch the client to force it to load the creds
|
||||
provider._dns_client
|
||||
|
||||
# set critical functions to return properly
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
tm_list.return_value = []
|
||||
tm_sync = provider._tm_client.profiles.create_or_update
|
||||
|
||||
def side_effect(rg, name, profile):
|
||||
return profile
|
||||
|
||||
tm_sync.side_effect = side_effect
|
||||
|
||||
return provider
|
||||
|
||||
def _get_dynamic_record(self, zone):
|
||||
return Record.new(zone, 'foo', data={
|
||||
'type': 'CNAME',
|
||||
'ttl': 60,
|
||||
'value': 'default.unit.tests.',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [
|
||||
{'value': 'one.unit.tests.', 'weight': 1},
|
||||
],
|
||||
'fallback': 'two',
|
||||
},
|
||||
'two': {
|
||||
'values': [
|
||||
{'value': 'two1.unit.tests.', 'weight': 3},
|
||||
{'value': 'two2.unit.tests.', 'weight': 4},
|
||||
],
|
||||
'fallback': 'three',
|
||||
},
|
||||
'three': {
|
||||
'values': [
|
||||
{'value': 'three.unit.tests.', 'weight': 1},
|
||||
],
|
||||
},
|
||||
},
|
||||
'rules': [
|
||||
{'geos': ['AF', 'EU-DE', 'NA-US-CA'], 'pool': 'one'},
|
||||
{'pool': 'two'},
|
||||
],
|
||||
},
|
||||
'octodns': {
|
||||
'healthcheck': {
|
||||
'path': '/_ping',
|
||||
'port': 4443,
|
||||
'protocol': 'HTTPS',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
def _get_tm_profiles(self, provider):
|
||||
sub = provider._dns_client_subscription_id
|
||||
rg = provider._resource_group
|
||||
base_id = '/subscriptions/' + sub + \
|
||||
'/resourceGroups/' + rg + \
|
||||
'/providers/Microsoft.Network/trafficManagerProfiles/'
|
||||
suffix = 'foo-unit-tests'
|
||||
id_format = base_id + '{}--' + suffix
|
||||
name_format = '{}--' + suffix
|
||||
|
||||
dns = DnsConfig(ttl=60)
|
||||
header = MonitorConfigCustomHeadersItem(name='Host',
|
||||
value='foo.unit.tests')
|
||||
monitor = MonitorConfig(protocol='HTTPS', port=4443, path='/_ping',
|
||||
custom_headers=[header])
|
||||
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
|
||||
nested = 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
|
||||
return [
|
||||
Profile(
|
||||
id=id_format.format('pool-two'),
|
||||
name=name_format.format('pool-two'),
|
||||
traffic_routing_method='Weighted',
|
||||
dns_config=dns,
|
||||
monitor_config=monitor,
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='two1.unit.tests',
|
||||
type=external,
|
||||
target='two1.unit.tests',
|
||||
weight=3,
|
||||
),
|
||||
Endpoint(
|
||||
name='two2.unit.tests',
|
||||
type=external,
|
||||
target='two2.unit.tests',
|
||||
weight=4,
|
||||
),
|
||||
],
|
||||
),
|
||||
Profile(
|
||||
id=id_format.format('rule-one'),
|
||||
name=name_format.format('rule-one'),
|
||||
traffic_routing_method='Priority',
|
||||
dns_config=dns,
|
||||
monitor_config=monitor,
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='one',
|
||||
type=external,
|
||||
target='one.unit.tests',
|
||||
priority=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='two',
|
||||
type=nested,
|
||||
target_resource_id=id_format.format('pool-two'),
|
||||
priority=2,
|
||||
),
|
||||
Endpoint(
|
||||
name='three',
|
||||
type=external,
|
||||
target='three.unit.tests',
|
||||
priority=3,
|
||||
),
|
||||
Endpoint(
|
||||
name='--default--',
|
||||
type=external,
|
||||
target='default.unit.tests',
|
||||
priority=4,
|
||||
),
|
||||
],
|
||||
),
|
||||
Profile(
|
||||
id=id_format.format('rule-two'),
|
||||
name=name_format.format('rule-two'),
|
||||
traffic_routing_method='Priority',
|
||||
dns_config=dns,
|
||||
monitor_config=monitor,
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='two',
|
||||
type=nested,
|
||||
target_resource_id=id_format.format('pool-two'),
|
||||
priority=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='three',
|
||||
type=external,
|
||||
target='three.unit.tests',
|
||||
priority=2,
|
||||
),
|
||||
Endpoint(
|
||||
name='--default--',
|
||||
type=external,
|
||||
target='default.unit.tests',
|
||||
priority=3,
|
||||
),
|
||||
],
|
||||
),
|
||||
Profile(
|
||||
id=base_id + suffix,
|
||||
name=suffix,
|
||||
traffic_routing_method='Geographic',
|
||||
dns_config=dns,
|
||||
monitor_config=monitor,
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
geo_mapping=['GEO-AF', 'DE', 'US-CA'],
|
||||
name='rule-one',
|
||||
type=nested,
|
||||
target_resource_id=id_format.format('rule-one'),
|
||||
),
|
||||
Endpoint(
|
||||
geo_mapping=['WORLD'],
|
||||
name='rule-two',
|
||||
type=nested,
|
||||
target_resource_id=id_format.format('rule-two'),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
def _get_dynamic_package(self):
|
||||
'''Convenience function to setup a sample dynamic record.
|
||||
'''
|
||||
provider = self._get_provider()
|
||||
|
||||
# setup traffic manager profiles
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
tm_list.return_value = self._get_tm_profiles(provider)
|
||||
|
||||
# setup zone with dynamic record
|
||||
zone = Zone(name='unit.tests.', sub_zones=[])
|
||||
record = self._get_dynamic_record(zone)
|
||||
zone.add_record(record)
|
||||
|
||||
# return everything
|
||||
return provider, zone, record
|
||||
|
||||
def test_populate_records(self):
|
||||
provider = self._get_provider()
|
||||
|
||||
@@ -510,6 +841,121 @@ class TestAzureDnsProvider(TestCase):
|
||||
self.assertEquals(len(zone.records), 17)
|
||||
self.assertTrue(exists)
|
||||
|
||||
def test_populate_dynamic(self):
|
||||
# Middle east without Asia raises exception
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
tm_suffix = _traffic_manager_suffix(record)
|
||||
tm_id = provider._profile_name_to_id
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
rule_name = 'rule-one--{}'.format(tm_suffix)
|
||||
nested = 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
tm_list.return_value = [
|
||||
Profile(
|
||||
id=tm_id(tm_suffix),
|
||||
name=tm_suffix,
|
||||
traffic_routing_method='Geographic',
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
geo_mapping=['GEO-ME'],
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
azrecord = RecordSet(
|
||||
ttl=60,
|
||||
target_resource=SubResource(id=tm_id(tm_suffix)),
|
||||
)
|
||||
azrecord.name = record.name or '@'
|
||||
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
|
||||
with self.assertRaises(AzureException) as ctx:
|
||||
provider._populate_record(zone, azrecord)
|
||||
self.assertTrue(text_type(ctx).startswith(
|
||||
'Middle East (GEO-ME) is not supported'
|
||||
))
|
||||
|
||||
# empty priority profile raises exception
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
rule_name = 'rule-one--{}'.format(tm_suffix)
|
||||
nested = 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
tm_list.return_value = [
|
||||
Profile(
|
||||
id=tm_id(rule_name),
|
||||
name=rule_name,
|
||||
traffic_routing_method='Priority',
|
||||
endpoints=[],
|
||||
),
|
||||
Profile(
|
||||
id=tm_id(tm_suffix),
|
||||
name=tm_suffix,
|
||||
traffic_routing_method='Geographic',
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
geo_mapping=['WORLD'],
|
||||
name='rule-one',
|
||||
type=nested,
|
||||
target_resource_id=tm_id(rule_name),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
with self.assertRaises(AzureException) as ctx:
|
||||
provider._populate_record(zone, azrecord)
|
||||
self.assertTrue(text_type(ctx).startswith(
|
||||
'Expected at least 2 endpoints'
|
||||
))
|
||||
|
||||
# valid set of profiles produce expected dynamic record
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
root_profile_id = provider._profile_name_to_id(
|
||||
_traffic_manager_suffix(record)
|
||||
)
|
||||
azrecord = RecordSet(
|
||||
ttl=60,
|
||||
target_resource=SubResource(id=root_profile_id),
|
||||
)
|
||||
azrecord.name = record.name or '@'
|
||||
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
|
||||
|
||||
record = provider._populate_record(zone, azrecord)
|
||||
self.assertEqual(record.name, 'foo')
|
||||
self.assertEqual(record.ttl, 60)
|
||||
self.assertEqual(record.value, 'default.unit.tests.')
|
||||
self.assertEqual(record.dynamic._data(), {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [
|
||||
{'value': 'one.unit.tests.', 'weight': 1},
|
||||
],
|
||||
'fallback': 'two',
|
||||
},
|
||||
'two': {
|
||||
'values': [
|
||||
{'value': 'two1.unit.tests.', 'weight': 3},
|
||||
{'value': 'two2.unit.tests.', 'weight': 4},
|
||||
],
|
||||
'fallback': 'three',
|
||||
},
|
||||
'three': {
|
||||
'values': [
|
||||
{'value': 'three.unit.tests.', 'weight': 1},
|
||||
],
|
||||
'fallback': None,
|
||||
},
|
||||
},
|
||||
'rules': [
|
||||
{'geos': ['AF', 'EU-DE', 'NA-US-CA'], 'pool': 'one'},
|
||||
{'pool': 'two'},
|
||||
],
|
||||
})
|
||||
|
||||
# valid profiles with Middle East test case
|
||||
geo_profile = provider._get_tm_for_dynamic_record(record)
|
||||
geo_profile.endpoints[0].geo_mapping.extend(['GEO-ME', 'GEO-AS'])
|
||||
record = provider._populate_record(zone, azrecord)
|
||||
self.assertIn('AS', record.dynamic.rules[0].data['geos'])
|
||||
self.assertNotIn('ME', record.dynamic.rules[0].data['geos'])
|
||||
|
||||
def test_populate_zone(self):
|
||||
provider = self._get_provider()
|
||||
|
||||
@@ -541,20 +987,384 @@ class TestAzureDnsProvider(TestCase):
|
||||
None
|
||||
)
|
||||
|
||||
def test_extra_changes(self):
|
||||
provider, existing, record = self._get_dynamic_package()
|
||||
|
||||
# test simple records produce no extra changes
|
||||
desired = Zone(name=existing.name, sub_zones=[])
|
||||
desired.add_record(Record.new(desired, 'simple', data={
|
||||
'type': record._type,
|
||||
'ttl': record.ttl,
|
||||
'value': record.value,
|
||||
}))
|
||||
extra = provider._extra_changes(desired, desired, [])
|
||||
self.assertEqual(len(extra), 0)
|
||||
|
||||
# test an unchanged dynamic record produces no extra changes
|
||||
desired.add_record(record)
|
||||
extra = provider._extra_changes(existing, desired, [])
|
||||
self.assertEqual(len(extra), 0)
|
||||
|
||||
# test unused TM produces the extra change for clean up
|
||||
sample_profile = self._get_tm_profiles(provider)[0]
|
||||
tm_id = provider._profile_name_to_id
|
||||
root_profile_name = _traffic_manager_suffix(record)
|
||||
extra_profile = Profile(
|
||||
id=tm_id('random--{}'.format(root_profile_name)),
|
||||
name='random--{}'.format(root_profile_name),
|
||||
traffic_routing_method='Weighted',
|
||||
dns_config=sample_profile.dns_config,
|
||||
monitor_config=sample_profile.monitor_config,
|
||||
endpoints=sample_profile.endpoints,
|
||||
)
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
tm_list.return_value.append(extra_profile)
|
||||
provider._populate_traffic_managers()
|
||||
extra = provider._extra_changes(existing, desired, [])
|
||||
self.assertEqual(len(extra), 1)
|
||||
extra = extra[0]
|
||||
self.assertIsInstance(extra, Update)
|
||||
self.assertEqual(extra.new, record)
|
||||
desired._remove_record(record)
|
||||
tm_list.return_value.pop()
|
||||
|
||||
# test new dynamic record does not produce an extra change for it
|
||||
new_dynamic = Record.new(desired, record.name + '2', data={
|
||||
'type': record._type,
|
||||
'ttl': record.ttl,
|
||||
'value': record.value,
|
||||
'dynamic': record.dynamic._data(),
|
||||
'octodns': record._octodns,
|
||||
})
|
||||
# test change in healthcheck by using a different port number
|
||||
update_dynamic = Record.new(desired, record.name, data={
|
||||
'type': record._type,
|
||||
'ttl': record.ttl,
|
||||
'value': record.value,
|
||||
'dynamic': record.dynamic._data(),
|
||||
'octodns': {
|
||||
'healthcheck': {
|
||||
'path': '/_ping',
|
||||
'port': 443,
|
||||
'protocol': 'HTTPS',
|
||||
},
|
||||
},
|
||||
})
|
||||
desired.add_record(new_dynamic)
|
||||
desired.add_record(update_dynamic)
|
||||
changes = [Create(new_dynamic)]
|
||||
extra = provider._extra_changes(existing, desired, changes)
|
||||
# implicitly asserts that new_dynamic was not added to extra changes
|
||||
# as it was already in the `changes` list
|
||||
self.assertEqual(len(extra), 1)
|
||||
extra = extra[0]
|
||||
self.assertIsInstance(extra, Update)
|
||||
self.assertEqual(extra.new, update_dynamic)
|
||||
|
||||
# test non-CNAME dynamic record throws exception
|
||||
a_dynamic = Record.new(desired, record.name + '3', data={
|
||||
'type': 'A',
|
||||
'ttl': record.ttl,
|
||||
'values': ['1.1.1.1'],
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {'values': [{'value': '2.2.2.2'}]},
|
||||
},
|
||||
'rules': [
|
||||
{'pool': 'one'},
|
||||
],
|
||||
},
|
||||
})
|
||||
desired.add_record(a_dynamic)
|
||||
changes.append(Create(a_dynamic))
|
||||
with self.assertRaises(AzureException):
|
||||
provider._extra_changes(existing, desired, changes)
|
||||
|
||||
def test_generate_tm_profile(self):
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
profile_gen = provider._generate_tm_profile
|
||||
|
||||
name = 'foobar'
|
||||
routing = 'Priority'
|
||||
endpoints = [
|
||||
Endpoint(target='one.unit.tests'),
|
||||
Endpoint(target_resource_id='/s/1/rg/foo/tm/foobar2'),
|
||||
Endpoint(name='invalid'),
|
||||
]
|
||||
|
||||
# invalid endpoint raises exception
|
||||
with self.assertRaises(AzureException):
|
||||
profile_gen(name, routing, endpoints, record)
|
||||
|
||||
# regular test
|
||||
endpoints.pop()
|
||||
profile = profile_gen(name, routing, endpoints, record)
|
||||
|
||||
# implicitly tests _profile_name_to_id
|
||||
sub = provider._dns_client_subscription_id
|
||||
rg = provider._resource_group
|
||||
expected_id = '/subscriptions/' + sub + \
|
||||
'/resourceGroups/' + rg + \
|
||||
'/providers/Microsoft.Network/trafficManagerProfiles/' + name
|
||||
self.assertEqual(profile.id, expected_id)
|
||||
self.assertEqual(profile.name, name)
|
||||
self.assertEqual(profile.traffic_routing_method, routing)
|
||||
self.assertEqual(profile.dns_config.ttl, record.ttl)
|
||||
self.assertEqual(len(profile.endpoints), len(endpoints))
|
||||
|
||||
self.assertEqual(
|
||||
profile.endpoints[0].type,
|
||||
'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
|
||||
)
|
||||
self.assertEqual(
|
||||
profile.endpoints[1].type,
|
||||
'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
)
|
||||
|
||||
def test_generate_traffic_managers(self):
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
profiles = provider._generate_traffic_managers(record)
|
||||
deduped = []
|
||||
seen = set()
|
||||
for profile in profiles:
|
||||
if profile.name not in seen:
|
||||
deduped.append(profile)
|
||||
seen.add(profile.name)
|
||||
|
||||
# check that every profile is a match with what we expect
|
||||
expected_profiles = self._get_tm_profiles(provider)
|
||||
self.assertEqual(len(expected_profiles), len(deduped))
|
||||
for have, expected in zip(deduped, expected_profiles):
|
||||
self.assertTrue(_profile_is_match(have, expected))
|
||||
|
||||
# check Asia/Middle East test case
|
||||
record.dynamic._data()['rules'][0]['geos'].append('AS')
|
||||
profiles = provider._generate_traffic_managers(record)
|
||||
geo_profile_name = _traffic_manager_suffix(record)
|
||||
geo_profile = next(
|
||||
profile
|
||||
for profile in profiles
|
||||
if profile.name == geo_profile_name
|
||||
)
|
||||
self.assertIn('GEO-ME', geo_profile.endpoints[0].geo_mapping)
|
||||
self.assertIn('GEO-AS', geo_profile.endpoints[0].geo_mapping)
|
||||
|
||||
def test_sync_traffic_managers(self):
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
provider._populate_traffic_managers()
|
||||
|
||||
tm_sync = provider._tm_client.profiles.create_or_update
|
||||
|
||||
suffix = 'foo-unit-tests'
|
||||
expected_seen = {
|
||||
suffix, 'pool-two--{}'.format(suffix),
|
||||
'rule-one--{}'.format(suffix), 'rule-two--{}'.format(suffix),
|
||||
}
|
||||
|
||||
# test no change
|
||||
seen = provider._sync_traffic_managers(record)
|
||||
self.assertEqual(seen, expected_seen)
|
||||
tm_sync.assert_not_called()
|
||||
|
||||
# test that changing weight causes update API call
|
||||
dynamic = record.dynamic._data()
|
||||
dynamic['pools']['two']['values'][0]['weight'] = 14
|
||||
data = {
|
||||
'type': 'CNAME',
|
||||
'ttl': record.ttl,
|
||||
'value': record.value,
|
||||
'dynamic': dynamic,
|
||||
'octodns': record._octodns,
|
||||
}
|
||||
new_record = Record.new(zone, record.name, data)
|
||||
tm_sync.reset_mock()
|
||||
seen2 = provider._sync_traffic_managers(new_record)
|
||||
self.assertEqual(seen2, expected_seen)
|
||||
tm_sync.assert_called_once()
|
||||
|
||||
# test that new profile was successfully inserted in cache
|
||||
new_profile = provider._get_tm_profile_by_name(
|
||||
'pool-two--{}'.format(suffix)
|
||||
)
|
||||
self.assertEqual(new_profile.endpoints[0].weight, 14)
|
||||
|
||||
def test_find_traffic_managers(self):
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
|
||||
# insert a non-matching profile
|
||||
sample_profile = self._get_tm_profiles(provider)[0]
|
||||
# dummy record for generating suffix
|
||||
record2 = Record.new(zone, record.name + '2', data={
|
||||
'type': record._type,
|
||||
'ttl': record.ttl,
|
||||
'value': record.value,
|
||||
})
|
||||
suffix2 = _traffic_manager_suffix(record2)
|
||||
tm_id = provider._profile_name_to_id
|
||||
extra_profile = Profile(
|
||||
id=tm_id('random--{}'.format(suffix2)),
|
||||
name='random--{}'.format(suffix2),
|
||||
traffic_routing_method='Weighted',
|
||||
dns_config=sample_profile.dns_config,
|
||||
monitor_config=sample_profile.monitor_config,
|
||||
endpoints=sample_profile.endpoints,
|
||||
)
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
tm_list.return_value.append(extra_profile)
|
||||
provider._populate_traffic_managers()
|
||||
|
||||
# implicitly asserts that non-matching profile is not included
|
||||
suffix = _traffic_manager_suffix(record)
|
||||
self.assertEqual(provider._find_traffic_managers(record), {
|
||||
suffix, 'pool-two--{}'.format(suffix),
|
||||
'rule-one--{}'.format(suffix), 'rule-two--{}'.format(suffix),
|
||||
})
|
||||
|
||||
def test_traffic_manager_gc(self):
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
provider._populate_traffic_managers()
|
||||
|
||||
profiles = provider._find_traffic_managers(record)
|
||||
profile_delete_mock = provider._tm_client.profiles.delete
|
||||
|
||||
provider._traffic_managers_gc(record, profiles)
|
||||
profile_delete_mock.assert_not_called()
|
||||
|
||||
profile_delete_mock.reset_mock()
|
||||
remove = list(profiles)[3]
|
||||
profiles.discard(remove)
|
||||
|
||||
provider._traffic_managers_gc(record, profiles)
|
||||
profile_delete_mock.assert_has_calls(
|
||||
[call(provider._resource_group, remove)]
|
||||
)
|
||||
|
||||
def test_apply(self):
|
||||
provider = self._get_provider()
|
||||
|
||||
changes = []
|
||||
deletes = []
|
||||
for i in octo_records:
|
||||
changes.append(Create(i))
|
||||
deletes.append(Delete(i))
|
||||
half = int(len(octo_records) / 2)
|
||||
changes = [Create(r) for r in octo_records[:half]] + \
|
||||
[Update(r, r) for r in octo_records[half:]]
|
||||
deletes = [Delete(r) for r in octo_records]
|
||||
|
||||
self.assertEquals(19, provider.apply(Plan(None, zone,
|
||||
changes, True)))
|
||||
self.assertEquals(19, provider.apply(Plan(zone, zone,
|
||||
deletes, True)))
|
||||
|
||||
def test_apply_create_dynamic(self):
|
||||
provider = self._get_provider()
|
||||
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
tm_list.return_value = []
|
||||
|
||||
tm_sync = provider._tm_client.profiles.create_or_update
|
||||
|
||||
zone = Zone(name='unit.tests.', sub_zones=[])
|
||||
record = self._get_dynamic_record(zone)
|
||||
|
||||
profiles = self._get_tm_profiles(provider)
|
||||
|
||||
provider._apply_Create(Create(record))
|
||||
# create was called as many times as number of profiles required for
|
||||
# the dynamic record
|
||||
self.assertEqual(tm_sync.call_count, len(profiles))
|
||||
|
||||
create = provider._dns_client.record_sets.create_or_update
|
||||
create.assert_called_once()
|
||||
|
||||
def test_apply_update_dynamic(self):
|
||||
# existing is simple, new is dynamic
|
||||
provider = self._get_provider()
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
tm_list.return_value = []
|
||||
profiles = self._get_tm_profiles(provider)
|
||||
dynamic_record = self._get_dynamic_record(zone)
|
||||
simple_record = Record.new(zone, dynamic_record.name, data={
|
||||
'type': 'CNAME',
|
||||
'ttl': 3600,
|
||||
'value': 'cname.unit.tests.',
|
||||
})
|
||||
change = Update(simple_record, dynamic_record)
|
||||
provider._apply_Update(change)
|
||||
tm_sync, dns_update, tm_delete = (
|
||||
provider._tm_client.profiles.create_or_update,
|
||||
provider._dns_client.record_sets.create_or_update,
|
||||
provider._tm_client.profiles.delete
|
||||
)
|
||||
self.assertEqual(tm_sync.call_count, len(profiles))
|
||||
dns_update.assert_called_once()
|
||||
tm_delete.assert_not_called()
|
||||
|
||||
# existing is dynamic, new is simple
|
||||
provider, existing, dynamic_record = self._get_dynamic_package()
|
||||
profiles = self._get_tm_profiles(provider)
|
||||
change = Update(dynamic_record, simple_record)
|
||||
provider._apply_Update(change)
|
||||
tm_sync, dns_update, tm_delete = (
|
||||
provider._tm_client.profiles.create_or_update,
|
||||
provider._dns_client.record_sets.create_or_update,
|
||||
provider._tm_client.profiles.delete
|
||||
)
|
||||
tm_sync.assert_not_called()
|
||||
dns_update.assert_called_once()
|
||||
self.assertEqual(tm_delete.call_count, len(profiles))
|
||||
|
||||
# both are dynamic, healthcheck port is changed
|
||||
provider, existing, dynamic_record = self._get_dynamic_package()
|
||||
profiles = self._get_tm_profiles(provider)
|
||||
dynamic_record2 = self._get_dynamic_record(existing)
|
||||
dynamic_record2._octodns['healthcheck']['port'] += 1
|
||||
change = Update(dynamic_record, dynamic_record2)
|
||||
provider._apply_Update(change)
|
||||
tm_sync, dns_update, tm_delete = (
|
||||
provider._tm_client.profiles.create_or_update,
|
||||
provider._dns_client.record_sets.create_or_update,
|
||||
provider._tm_client.profiles.delete
|
||||
)
|
||||
self.assertEqual(tm_sync.call_count, len(profiles))
|
||||
dns_update.assert_not_called()
|
||||
tm_delete.assert_not_called()
|
||||
|
||||
# both are dynamic, extra profile should be deleted
|
||||
provider, existing, dynamic_record = self._get_dynamic_package()
|
||||
sample_profile = self._get_tm_profiles(provider)[0]
|
||||
tm_id = provider._profile_name_to_id
|
||||
root_profile_name = _traffic_manager_suffix(dynamic_record)
|
||||
extra_profile = Profile(
|
||||
id=tm_id('random--{}'.format(root_profile_name)),
|
||||
name='random--{}'.format(root_profile_name),
|
||||
traffic_routing_method='Weighted',
|
||||
dns_config=sample_profile.dns_config,
|
||||
monitor_config=sample_profile.monitor_config,
|
||||
endpoints=sample_profile.endpoints,
|
||||
)
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
tm_list.return_value.append(extra_profile)
|
||||
change = Update(dynamic_record, dynamic_record)
|
||||
provider._apply_Update(change)
|
||||
tm_sync, dns_update, tm_delete = (
|
||||
provider._tm_client.profiles.create_or_update,
|
||||
provider._dns_client.record_sets.create_or_update,
|
||||
provider._tm_client.profiles.delete
|
||||
)
|
||||
tm_sync.assert_not_called()
|
||||
dns_update.assert_not_called()
|
||||
tm_delete.assert_called_once()
|
||||
|
||||
def test_apply_delete_dynamic(self):
|
||||
provider, existing, record = self._get_dynamic_package()
|
||||
provider._populate_traffic_managers()
|
||||
profiles = self._get_tm_profiles(provider)
|
||||
change = Delete(record)
|
||||
provider._apply_Delete(change)
|
||||
dns_delete, tm_delete = (
|
||||
provider._dns_client.record_sets.delete,
|
||||
provider._tm_client.profiles.delete
|
||||
)
|
||||
dns_delete.assert_called_once()
|
||||
self.assertEqual(tm_delete.call_count, len(profiles))
|
||||
|
||||
def test_create_zone(self):
|
||||
provider = self._get_provider()
|
||||
|
||||
|
||||
@@ -3013,6 +3013,7 @@ class TestDynamicRecords(TestCase):
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [{
|
||||
'weight': 10,
|
||||
'value': '3.3.3.3',
|
||||
}],
|
||||
},
|
||||
@@ -3412,7 +3413,7 @@ class TestDynamicRecords(TestCase):
|
||||
self.assertEquals(['pool "one" is missing values'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
# pool valu not a dict
|
||||
# pool value not a dict
|
||||
a_data = {
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
@@ -3596,6 +3597,33 @@ class TestDynamicRecords(TestCase):
|
||||
self.assertEquals(['invalid weight "foo" in pool "three" value 2'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
# single value with weight!=1
|
||||
a_data = {
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [{
|
||||
'weight': 12,
|
||||
'value': '6.6.6.6',
|
||||
}],
|
||||
},
|
||||
},
|
||||
'rules': [{
|
||||
'pool': 'one',
|
||||
}],
|
||||
},
|
||||
'ttl': 60,
|
||||
'type': 'A',
|
||||
'values': [
|
||||
'1.1.1.1',
|
||||
'2.2.2.2',
|
||||
],
|
||||
}
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, 'bad', a_data)
|
||||
self.assertEquals(['pool "one" has single value with weight!=1'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
# invalid fallback
|
||||
a_data = {
|
||||
'dynamic': {
|
||||
|
||||
Reference in New Issue
Block a user