Exemple #1
0
    def test_apply_traffic_director(self, mock):
        provider = DynProvider('test',
                               'cust',
                               'user',
                               'pass',
                               traffic_directors_enabled=True)

        # stubbing these out to avoid a lot of messy mocking, they'll be tested
        # individually, we'll check for expected calls
        provider._mod_geo_Create = MagicMock()
        provider._mod_geo_Update = MagicMock()
        provider._mod_geo_Delete = MagicMock()
        provider._mod_Create = MagicMock()
        provider._mod_Update = MagicMock()
        provider._mod_Delete = MagicMock()

        # busted traffic director
        mock.side_effect = [
            # get zone
            {
                'data': {}
            },
            # accept publish
            {
                'data': {}
            },
        ]
        desired = Zone('unit.tests.', [])
        geo = self.geo_record
        regular = self.regular_record

        changes = [
            Create(geo),
            Create(regular),
            Update(geo, geo),
            Update(regular, regular),
            Delete(geo),
            Delete(regular),
        ]
        plan = Plan(None, desired, changes)
        provider._apply(plan)
        mock.assert_has_calls([
            call('/Zone/unit.tests/', 'GET', {}),
            call('/Zone/unit.tests/', 'PUT', {'publish': True})
        ])
        # should have seen 1 call to each
        provider._mod_geo_Create.assert_called_once()
        provider._mod_geo_Update.assert_called_once()
        provider._mod_geo_Delete.assert_called_once()
        provider._mod_Create.assert_called_once()
        provider._mod_Update.assert_called_once()
        provider._mod_Delete.assert_called_once()
Exemple #2
0
    def test_safe(self):
        ignored = Zone('unit.tests.', [])

        # No changes is safe
        Plan(None, None, []).raise_if_unsafe()

        # Creates are safe
        record = Record.new(ignored, 'a', {
            'ttl': 30,
            'type': 'A',
            'value': '1.2.3.4',
        })
        Plan(None, None, [Create(record) for i in range(10)]).raise_if_unsafe()

        # max Updates is safe
        changes = [
            Update(record, record) for i in range(Plan.MAX_SAFE_UPDATES)
        ]
        Plan(None, None, changes).raise_if_unsafe()
        # but max + 1 isn't
        with self.assertRaises(UnsafePlan):
            changes.append(Update(record, record))
            Plan(None, None, changes).raise_if_unsafe()

        # max Deletes is safe
        changes = [Delete(record) for i in range(Plan.MAX_SAFE_DELETES)]
        Plan(None, None, changes).raise_if_unsafe()
        # but max + 1 isn't
        with self.assertRaises(UnsafePlan):
            changes.append(Delete(record))
            Plan(None, None, changes).raise_if_unsafe()
Exemple #3
0
    def test_plan_sorts_changes_pass_to_it(self):
        # we aren't worried about the details of the sorting, that's tested in
        # test_octodns_record's TestChanges. We just want to make sure that the
        # changes are sorted at all.
        zone = Zone('unit.tests.', [])
        record_a_1 = Record.new(
            zone, '1', {'type': 'A', 'ttl': 30, 'value': '1.2.3.4'}
        )
        create_a_1 = Create(record_a_1)
        record_a_2 = Record.new(
            zone, '2', {'type': 'A', 'ttl': 30, 'value': '1.2.3.4'}
        )
        create_a_2 = Create(record_a_2)

        # passed in reverse of expected order
        plan = Plan(None, None, [create_a_2, create_a_1], False)
        self.assertEqual([create_a_1, create_a_2], plan.changes)
Exemple #4
0
    def test_safe_creates(self):
        # Creates are safe when existing records is under MIN_EXISTING_RECORDS
        zone = Zone('unit.tests.', [])

        record = Record.new(zone, 'a', {
            'ttl': 30,
            'type': 'A',
            'value': '1.2.3.4',
        })
        Plan(zone, zone, [Create(record) for i in range(10)]).raise_if_unsafe()
    def test_apply(self):
        provider = self._get_provider()

        changes = []
        deletes = []
        for i in octo_records:
            changes.append(Create(i))
            deletes.append(Delete(i))

        self.assertEquals(13, provider.apply(Plan(None, zone, changes)))
        self.assertEquals(13, provider.apply(Plan(zone, zone, deletes)))
Exemple #6
0
    def test_too_many_deletes(self):
        existing = self.existing.copy()
        changes = []

        # No records, no changes, we're good
        plan = HelperPlan(existing, None, changes, True)
        plan.raise_if_unsafe()

        # Four records, no changes, we're good
        existing.add_record(self.record_1)
        existing.add_record(self.record_2)
        existing.add_record(self.record_3)
        existing.add_record(self.record_4)
        plan = HelperPlan(existing, None, changes, True)
        plan.raise_if_unsafe()

        # Creates don't count against us
        changes.append(Create(self.record_1))
        changes.append(Create(self.record_2))
        changes.append(Create(self.record_3))
        changes.append(Create(self.record_4))
        plan = HelperPlan(existing, None, changes, True)
        plan.raise_if_unsafe()

        # One delete, still good (25%, default threshold is 33%)
        changes.append(Delete(self.record_1))
        plan = HelperPlan(existing, None, changes, True)
        plan.raise_if_unsafe()

        # Two and we're over the threshold
        changes.append(Delete(self.record_2))
        plan = HelperPlan(existing, None, changes, True)
        with self.assertRaises(TooMuchChange) as ctx:
            plan.raise_if_unsafe()
        self.assertTrue('Too many deletes', str(ctx.exception))

        # If we require more records before applying we're still OK though
        plan = HelperPlan(existing, None, changes, True, min_existing=10)
        plan.raise_if_unsafe()
Exemple #7
0
    def test_apply(self):
        ignored = Zone('unit.tests.', [])

        record = Record.new(ignored, 'a', {
            'ttl': 30,
            'type': 'A',
            'value': '1.2.3.4',
        })
        provider = HelperProvider([Create(record)], apply_disabled=True)
        plan = provider.plan(ignored)
        provider.apply(plan)

        provider.apply_disabled = False
        self.assertEquals(1, provider.apply(plan))
    def test_create_zone(self):
        provider = self._get_provider()

        changes = []
        for i in octo_records:
            changes.append(Create(i))
        desired = Zone('unit2.test.', [])

        err_msg = 'The Resource \'Microsoft.Network/dnszones/unit2.test\' '
        err_msg += 'under resource group \'mock_rg\' was not found.'
        _get = provider._dns_client.zones.get
        _get.side_effect = CloudError(Mock(status=404), err_msg)

        self.assertEquals(13, provider.apply(Plan(None, desired, changes)))
Exemple #9
0
    def test_mod_rulesets_create(self, _, ruleset_create_mock,
                                 add_response_pool_mock):
        provider = DynProvider('test',
                               'cust',
                               'user',
                               'pass',
                               traffic_directors_enabled=True)

        td_mock = MagicMock()
        td_mock._rulesets = []
        provider._traffic_director_monitor = MagicMock()
        provider._find_or_create_pool = MagicMock()

        td_mock.all_response_pools = []

        provider._find_or_create_pool.side_effect = [
            _DummyPool('default'),
            _DummyPool(1),
            _DummyPool(2),
            _DummyPool(3),
            _DummyPool(4),
        ]

        change = Create(self.geo_record)
        provider._mod_rulesets(td_mock, change)
        ruleset_create_mock.assert_has_calls((
            call(td_mock, index=0),
            call(td_mock, index=0),
            call(td_mock, index=0),
            call(td_mock, index=0),
            call(td_mock, index=0),
        ))
        add_response_pool_mock.assert_has_calls((
            # default
            call('default'),
            # first geo and it's fallback
            call(1),
            call('default', index=999),
            # 2nd geo and it's fallback
            call(2),
            call('default', index=999),
            # 3nd geo and it's fallback
            call(3),
            call('default', index=999),
            # 4th geo and it's 2 levels of fallback
            call(4),
            call(3, index=999),
            call('default', index=999),
        ))
Exemple #10
0
    def test_plan(self):
        ignored = Zone('unit.tests.', [])

        # No change, thus no plan
        provider = HelperProvider([])
        self.assertEquals(None, provider.plan(ignored))

        record = Record.new(ignored, 'a', {
            'ttl': 30,
            'type': 'A',
            'value': '1.2.3.4',
        })
        provider = HelperProvider([Create(record)])
        plan = provider.plan(ignored)
        self.assertTrue(plan)
        self.assertEquals(1, len(plan.changes))
 def test_change(self):
     existing = Record.new(self.zone, 'txt', {
         'ttl': 44,
         'type': 'TXT',
         'value': 'some text',
     })
     new = Record.new(self.zone, 'txt', {
         'ttl': 44,
         'type': 'TXT',
         'value': 'some change',
     })
     create = Create(new)
     self.assertEquals(new.values, create.record.values)
     update = Update(existing, new)
     self.assertEquals(new.values, update.record.values)
     delete = Delete(existing)
     self.assertEquals(existing.values, delete.record.values)
Exemple #12
0
    def test_safe_min_existing_creates(self):
        # Creates are safe when existing records is over MIN_EXISTING_RECORDS
        zone = Zone('unit.tests.', [])

        record = Record.new(zone, 'a', {
            'ttl': 30,
            'type': 'A',
            'value': '1.2.3.4',
        })

        for i in range(int(Plan.MIN_EXISTING_RECORDS)):
            zone.add_record(Record.new(zone, str(i), {
                            'ttl': 60,
                            'type': 'A',
                            'value': '2.3.4.5'
                            }))

        Plan(zone, zone, [Create(record) for i in range(10)]).raise_if_unsafe()
Exemple #13
0
    def test_mod_geo_create(self, mock):
        provider = DynProvider('test',
                               'cust',
                               'user',
                               'pass',
                               traffic_directors_enabled=True)

        # will be tested seperately
        provider._mod_rulesets = MagicMock()

        mock.side_effect = [
            # create traffic director
            self.traffic_director_response,
            # get traffic directors
            self.traffic_directors_reponse
        ]
        provider._mod_geo_Create(None, Create(self.geo_record))
        # td now lives in cache
        self.assertTrue('A' in provider.traffic_directors['unit.tests.'])
        # should have seen 1 gen call
        provider._mod_rulesets.assert_called_once()
    def test_extra_change_has_wrong_health_check(self):
        provider, stubber = self._get_stubbed_provider()

        list_hosted_zones_resp = {
            'HostedZones': [{
                'Name': 'unit.tests.',
                'Id': 'z42',
                'CallerReference': 'abc',
            }],
            'Marker':
            'm',
            'IsTruncated':
            False,
            'MaxItems':
            '100',
        }
        stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})

        # record with geo and no health check returns change
        desired = Zone('unit.tests.', [])
        record = Record.new(
            desired, 'a', {
                'ttl': 30,
                'type': 'A',
                'value': '1.2.3.4',
                'geo': {
                    'NA': ['2.2.3.4'],
                }
            })

        desired.add_record(record)
        extra = provider._extra_changes(desired, [])
        self.assertEquals(0, len(extra))
        stubber.assert_no_pending_responses()

        for change in (Create(record), Update(record, record), Delete(record)):
            extra = provider._extra_changes(desired=desired, changes=[change])
            self.assertEquals(0, len(extra))
            stubber.assert_no_pending_responses()
Exemple #15
0
    def test__apply(self, *_):
        class DummyDesired:
            def __init__(self, name, changes):
                self.name = name
                self.changes = changes

        apply_z = Zone("unit.tests.", [])
        create_r = Record.new(apply_z, '', {
            'ttl': 0,
            'type': 'A',
            'values': ['1.2.3.4', '10.10.10.10']
        })
        delete_r = Record.new(apply_z, 'a', {
            'ttl': 1,
            'type': 'A',
            'values': ['1.2.3.4', '1.1.1.1']
        })
        update_existing_r = Record.new(apply_z, 'aa', {
            'ttl': 9001,
            'type': 'A',
            'values': ['1.2.4.3']
        })
        update_new_r = Record.new(apply_z, 'aa', {
            'ttl': 666,
            'type': 'A',
            'values': ['1.4.3.2']
        })

        gcloud_zone_mock = DummyGoogleCloudZone("unit.tests.", "unit-tests")
        status_mock = Mock()
        return_values_for_status = iter(["pending"] * 11 + ['done', 'done'])
        type(status_mock).status = PropertyMock(
            side_effect=return_values_for_status.next)
        gcloud_zone_mock.changes = Mock(return_value=status_mock)

        provider = self._get_provider()
        provider.gcloud_client = Mock()
        provider._gcloud_zones = {"unit.tests.": gcloud_zone_mock}
        desired = Mock()
        desired.name = "unit.tests."
        changes = []
        changes.append(Create(create_r))
        changes.append(Delete(delete_r))
        changes.append(Update(existing=update_existing_r, new=update_new_r))

        provider.apply(
            Plan(existing=[update_existing_r, delete_r],
                 desired=desired,
                 changes=changes))

        calls_mock = gcloud_zone_mock.changes.return_value
        mocked_calls = []
        for mock_call in calls_mock.add_record_set.mock_calls:
            mocked_calls.append(mock_call[1][0])

        self.assertEqual(mocked_calls, [
            DummyResourceRecordSet('unit.tests.', 'A', 0,
                                   ['1.2.3.4', '10.10.10.10']),
            DummyResourceRecordSet('aa.unit.tests.', 'A', 666, ['1.4.3.2'])
        ])

        mocked_calls2 = []
        for mock_call in calls_mock.delete_record_set.mock_calls:
            mocked_calls2.append(mock_call[1][0])

        self.assertEqual(mocked_calls2, [
            DummyResourceRecordSet('a.unit.tests.', 'A', 1,
                                   ['1.2.3.4', '1.1.1.1']),
            DummyResourceRecordSet('aa.unit.tests.', 'A', 9001, ['1.2.4.3'])
        ])

        type(status_mock).status = "pending"

        with self.assertRaises(RuntimeError):
            provider.apply(
                Plan(existing=[update_existing_r, delete_r],
                     desired=desired,
                     changes=changes))

        unsupported_change = Mock()
        unsupported_change.__len__ = Mock(return_value=1)
        type_mock = Mock()
        type_mock._type = "A"
        unsupported_change.record = type_mock

        mock_plan = Mock()
        type(mock_plan).desired = PropertyMock(
            return_value=DummyDesired("dummy name", []))
        type(mock_plan).changes = [unsupported_change]

        with self.assertRaises(RuntimeError):
            provider.apply(mock_plan)
    def test_health_check_gc(self):
        provider, stubber = self._get_stubbed_provider()

        stubber.add_response('list_health_checks', {
            'HealthChecks': self.health_checks,
            'IsTruncated': False,
            'MaxItems': '100',
            'Marker': '',
        })

        record = Record.new(self.expected, '', {
            'ttl': 61,
            'type': 'A',
            'values': ['2.2.3.4', '3.2.3.4'],
            'geo': {
                'AF': ['4.2.3.4'],
                'NA-US': ['5.2.3.4', '6.2.3.4'],
                # removed one geo
            }
        })

        class DummyRecord(object):

            def __init__(self, health_check_id):
                self.health_check_id = health_check_id

        # gc no longer in_use records (directly)
        stubber.add_response('delete_health_check', {}, {
            'HealthCheckId': '44',
        })
        provider._gc_health_checks(record, [
            DummyRecord('42'),
            DummyRecord('43'),
        ])
        stubber.assert_no_pending_responses()

        # gc through _mod_Create
        stubber.add_response('delete_health_check', {}, {
            'HealthCheckId': '44',
        })
        change = Create(record)
        provider._mod_Create(change)
        stubber.assert_no_pending_responses()

        # gc through _mod_Update
        stubber.add_response('delete_health_check', {}, {
            'HealthCheckId': '44',
        })
        # first record is ignored for our purposes, we have to pass something
        change = Update(record, record)
        provider._mod_Create(change)
        stubber.assert_no_pending_responses()

        # gc through _mod_Delete, expect 3 to go away, can't check order
        # b/c it's not deterministic
        stubber.add_response('delete_health_check', {}, {
            'HealthCheckId': ANY,
        })
        stubber.add_response('delete_health_check', {}, {
            'HealthCheckId': ANY,
        })
        stubber.add_response('delete_health_check', {}, {
            'HealthCheckId': ANY,
        })
        change = Delete(record)
        provider._mod_Delete(change)
        stubber.assert_no_pending_responses()

        # gc only AAAA, leave the A's alone
        stubber.add_response('delete_health_check', {}, {
            'HealthCheckId': '45',
        })
        record = Record.new(self.expected, '', {
            'ttl': 61,
            'type': 'AAAA',
            'value': '2001:0db8:3c4d:0015:0000:0000:1a2f:1a4b'
        })
        provider._gc_health_checks(record, [])
        stubber.assert_no_pending_responses()
Exemple #17
0
    zone,
    'a',
    {
        'geo': {
            'AF': ['5.5.5.5'],
            'NA-US': ['6.6.6.6']
        },
        'ttl': 300,
        'type': 'A',
        # This leaves one, swaps ones, and adds one
        'values': ['2.2.2.2', '3.3.3.3', '4.4.4.4'],
    },
    simple)
create = Create(
    Record.new(zone, 'b', {
        'ttl': 60,
        'type': 'CNAME',
        'value': 'foo.unit.tests.'
    }, simple))
create2 = Create(
    Record.new(zone, 'c', {
        'ttl': 60,
        'type': 'CNAME',
        'value': 'foo.unit.tests.'
    }))
update = Update(existing, new)
delete = Delete(new)
changes = [create, create2, delete, update]
plans = [
    (simple, Plan(zone, zone, changes, True)),
    (simple, Plan(zone, zone, changes, False)),
]
    def test_extra_change_has_wrong_health_check(self):
        provider, stubber = self._get_stubbed_provider()

        list_hosted_zones_resp = {
            'HostedZones': [{
                'Name': 'unit.tests.',
                'Id': 'z42',
                'CallerReference': 'abc',
            }],
            'Marker': 'm',
            'IsTruncated': False,
            'MaxItems': '100',
        }
        stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})

        # record with geo and no health check returns change
        existing = Zone('unit.tests.', [])
        record = Record.new(existing, 'a', {
            'ttl': 30,
            'type': 'A',
            'value': '1.2.3.4',
            'geo': {
                'NA': ['2.2.3.4'],
            }
        })
        existing.add_record(record)
        list_resource_record_sets_resp = {
            'ResourceRecordSets': [{
                'Name': 'a.unit.tests.',
                'Type': 'A',
                'GeoLocation': {
                    'ContinentCode': 'NA',
                },
                'ResourceRecords': [{
                    'Value': '2.2.3.4',
                }],
                'TTL': 61,
                'HealthCheckId': '42',
            }],
            'IsTruncated': False,
            'MaxItems': '100',
        }
        stubber.add_response('list_resource_record_sets',
                             list_resource_record_sets_resp,
                             {'HostedZoneId': 'z42'})
        stubber.add_response('list_health_checks', {
            'HealthChecks': [{
                'Id': '42',
                'CallerReference': 'foo',
                'HealthCheckConfig': {
                    'Type': 'HTTPS',
                    'FullyQualifiedDomainName': 'unit.tests',
                    'IPAddress': '2.2.3.4',
                },
                'HealthCheckVersion': 2,
            }],
            'IsTruncated': False,
            'MaxItems': '100',
            'Marker': '',
        })
        extra = provider._extra_changes(existing, [])
        self.assertEquals(1, len(extra))
        stubber.assert_no_pending_responses()

        for change in (Create(record), Update(record, record), Delete(record)):
            extra = provider._extra_changes(existing, [change])
            self.assertEquals(0, len(extra))
            stubber.assert_no_pending_responses()
Exemple #19
0
    def test_mod_rulesets_existing(self, _, ruleset_create_mock,
                                   add_response_pool_mock,
                                   get_response_pool_mock):
        provider = DynProvider('test',
                               'cust',
                               'user',
                               'pass',
                               traffic_directors_enabled=True)

        ruleset_mock = MagicMock()
        ruleset_mock.response_pools = [_DummyPool(3)]

        td_mock = MagicMock()
        td_mock._rulesets = [
            ruleset_mock,
        ]
        provider._traffic_director_monitor = MagicMock()
        provider._find_or_create_pool = MagicMock()

        unused_pool = _DummyPool('unused')
        td_mock.all_response_pools = \
            ruleset_mock.response_pools + [unused_pool]
        get_response_pool_mock.return_value = unused_pool

        provider._find_or_create_pool.side_effect = [
            _DummyPool('default'),
            _DummyPool(1),
            _DummyPool(2),
            ruleset_mock.response_pools[0],
            _DummyPool(4),
        ]

        change = Create(self.geo_record)
        provider._mod_rulesets(td_mock, change)
        ruleset_create_mock.assert_has_calls((
            call(td_mock, index=0),
            call(td_mock, index=0),
            call(td_mock, index=0),
            call(td_mock, index=0),
            call(td_mock, index=0),
        ))
        add_response_pool_mock.assert_has_calls((
            # default
            call('default'),
            # first geo and it's fallback
            call(1),
            call('default', index=999),
            # 2nd geo and it's fallback
            call(2),
            call('default', index=999),
            # 3nd geo, from existing, and it's fallback
            call(3),
            call('default', index=999),
            # 4th geo and it's 2 levels of fallback
            call(4),
            call(3, index=999),
            call('default', index=999),
        ))
        # unused poll should have been deleted
        self.assertTrue(unused_pool.deleted)
        # old ruleset ruleset should be deleted, it's pool will have been
        # reused
        ruleset_mock.delete.assert_called_once()