def _onBody(self, body, response, user_callback, data, headers): self._logHeaders(headers) self._log.debug("%s %s %s %s" % (response.request.method, response.request.absoluteURI, response.code, data)) if response.code != 200: if response.code == 429: raise RateLimitException('rate limit exceeded', response, body) elif response.code == 401: raise AuthException('unauthorized', response, body) else: raise ResourceException('server error', response, body) try: jsonOut = json.loads(body) except: raise ResourceException('invalid json in response', response, body) if user_callback: # set these in case callback throws, so we have them for errback self.response = response self.body = body return user_callback(jsonOut, body, response) else: return jsonOut
def _errback(self, failure, user_errback): # print "failure: %s" % failure.printTraceback() if user_errback: return user_errback(failure) else: if failure.check(ResourceException, RateLimitException): raise failure.value raise ResourceException(failure.getErrorMessage())
def test_populate(self, load_mock): provider = Ns1Provider('test', 'api-key') # Bad auth load_mock.side_effect = AuthException('unauthorized') zone = Zone('unit.tests.', []) with self.assertRaises(AuthException) as ctx: provider.populate(zone) self.assertEquals(load_mock.side_effect, ctx.exception) # General error load_mock.reset_mock() load_mock.side_effect = ResourceException('boom') zone = Zone('unit.tests.', []) with self.assertRaises(ResourceException) as ctx: provider.populate(zone) self.assertEquals(load_mock.side_effect, ctx.exception) self.assertEquals(('unit.tests', ), load_mock.call_args[0]) # Non-existant zone doesn't populate anything load_mock.reset_mock() load_mock.side_effect = \ ResourceException('server error: zone not found') zone = Zone('unit.tests.', []) provider.populate(zone) self.assertEquals(set(), zone.records) self.assertEquals(('unit.tests', ), load_mock.call_args[0]) # Existing zone w/o records load_mock.reset_mock() nsone_zone = DummyZone([]) load_mock.side_effect = [nsone_zone] zone = Zone('unit.tests.', []) provider.populate(zone) self.assertEquals(set(), zone.records) self.assertEquals(('unit.tests', ), load_mock.call_args[0]) # Existing zone w/records load_mock.reset_mock() nsone_zone = DummyZone(self.nsone_records) load_mock.side_effect = [nsone_zone] zone = Zone('unit.tests.', []) provider.populate(zone) self.assertEquals(self.expected, zone.records) self.assertEquals(('unit.tests', ), load_mock.call_args[0])
def handleProblem(code, resp, msg): if errback: errback((resp, msg)) return if code == 429: raise RateLimitException('rate limit exceeded', resp, msg) elif code == 401: raise AuthException('unauthorized', resp, msg) else: raise ResourceException('server error', resp, msg)
def send(self, method, url, headers=None, data=None, files=None, callback=None, errback=None): if files is not None: # XXX raise Exception('file uploads not supported in BasicTransport yet') self._logHeaders(headers) self._log.debug("%s %s %s" % (method, url, data)) opener = build_opener(HTTPSHandler) request = Request(url, headers=headers, data=data) request.get_method = lambda: method def handleProblem(code, resp, msg): if errback: errback((resp, msg)) return if code == 429: raise RateLimitException('rate limit exceeded', resp, msg) elif code == 401: raise AuthException('unauthorized', resp, msg) else: raise ResourceException('server error', resp, msg) # Handle error and responses the same so we can # always pass the body to the handleProblem function try: resp = opener.open(request) except HTTPError as e: resp = e finally: body = resp.read() if resp.code != 200: handleProblem(resp.code, resp, body) # TODO make sure json is valid try: jsonOut = json.loads(body) except ValueError: if errback: errback(resp) return else: raise ResourceException('invalid json in response', resp, resp.text) if callback: return callback(jsonOut) else: return jsonOut
def send(self, method, url, headers=None, data=None, files=None, callback=None, errback=None): self._logHeaders(headers) resp = self.REQ_MAP[method](url, headers=headers, verify=self._verify, data=data, files=files) if resp.status_code != 200: if errback: errback(resp) return else: if resp.status_code == 429: raise RateLimitException('rate limit exceeded', resp, resp.text) elif resp.status_code == 401: raise AuthException('unauthorized', resp, resp.text) else: raise ResourceException('server error', resp, resp.text) # TODO make sure json is valid try: jsonOut = resp.json() except ValueError: if errback: errback(resp) return else: raise ResourceException('invalid json in response', resp, resp.text) if callback: return callback(jsonOut) else: return jsonOut
def __init__(self, config): """ :param nsone.config.Config config: config object used to build requests """ self._config = config self._log = logging.getLogger(__name__) # TODO verify we have a default key # get a transport. TODO make this static property? transport = self._config.get('transport', None) if transport is None: # for default transport: # if requests is available, use that. otherwise, basic from nsone.rest.transport.requests import have_requests if have_requests: transport = 'requests' else: transport = 'basic' if transport not in TransportBase.REGISTRY: raise ResourceException('requested transport was not found: %s' % transport) self._transport = TransportBase.REGISTRY[transport](self._config)
def test_sync(self, load_mock, create_mock): provider = Ns1Provider('test', 'api-key') desired = Zone('unit.tests.', []) desired.records.update(self.expected) plan = provider.plan(desired) # everything except the root NS expected_n = len(self.expected) - 1 self.assertEquals(expected_n, len(plan.changes)) # Fails, general error load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = ResourceException('boom') with self.assertRaises(ResourceException) as ctx: provider.apply(plan) self.assertEquals(load_mock.side_effect, ctx.exception) # Fails, bad auth load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = \ ResourceException('server error: zone not found') create_mock.side_effect = AuthException('unauthorized') with self.assertRaises(AuthException) as ctx: provider.apply(plan) self.assertEquals(create_mock.side_effect, ctx.exception) # non-existant zone, create load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = \ ResourceException('server error: zone not found') create_mock.side_effect = None got_n = provider.apply(plan) self.assertEquals(expected_n, got_n) # Update & delete load_mock.reset_mock() create_mock.reset_mock() nsone_zone = DummyZone(self.nsone_records + [{ 'type': 'A', 'ttl': 42, 'short_answers': ['9.9.9.9'], 'domain': 'delete-me.unit.tests.', }]) nsone_zone.data['records'][0]['short_answers'][0] = '2.2.2.2' nsone_zone.loadRecord = Mock() load_mock.side_effect = [nsone_zone, nsone_zone] plan = provider.plan(desired) self.assertEquals(2, len(plan.changes)) self.assertIsInstance(plan.changes[0], Update) self.assertIsInstance(plan.changes[1], Delete) got_n = provider.apply(plan) self.assertEquals(2, got_n) nsone_zone.loadRecord.assert_has_calls([ call('unit.tests', u'A'), call().update(answers=[u'1.2.3.4'], ttl=32), call('delete-me', u'A'), call().delete() ])
def test_sync(self, load_mock, create_mock): provider = Ns1Provider('test', 'api-key') desired = Zone('unit.tests.', []) for r in self.expected: desired.add_record(r) plan = provider.plan(desired) # everything except the root NS expected_n = len(self.expected) - 1 self.assertEquals(expected_n, len(plan.changes)) # Fails, general error load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = ResourceException('boom') with self.assertRaises(ResourceException) as ctx: provider.apply(plan) self.assertEquals(load_mock.side_effect, ctx.exception) # Fails, bad auth load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = \ ResourceException('server error: zone not found') create_mock.side_effect = AuthException('unauthorized') with self.assertRaises(AuthException) as ctx: provider.apply(plan) self.assertEquals(create_mock.side_effect, ctx.exception) # non-existant zone, create load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = \ ResourceException('server error: zone not found') # ugh, need a mock zone with a mock prop since we're using getattr, we # can actually control side effects on `meth` with that. mock_zone = Mock() mock_zone.add_SRV = Mock() mock_zone.add_SRV.side_effect = [ RateLimitException('boo', period=0), None, ] create_mock.side_effect = [mock_zone] got_n = provider.apply(plan) self.assertEquals(expected_n, got_n) # Update & delete load_mock.reset_mock() create_mock.reset_mock() nsone_zone = DummyZone(self.nsone_records + [{ 'type': 'A', 'ttl': 42, 'short_answers': ['9.9.9.9'], 'domain': 'delete-me.unit.tests.', }]) nsone_zone.data['records'][0]['short_answers'][0] = '2.2.2.2' nsone_zone.loadRecord = Mock() zone_search = Mock() zone_search.return_value = [ { "domain": "geo.unit.tests", "zone": "unit.tests", "type": "A", "answers": [ {'answer': ['1.1.1.1'], 'meta': {}}, {'answer': ['1.2.3.4'], 'meta': {'ca_province': ['ON']}}, {'answer': ['2.3.4.5'], 'meta': {'us_state': ['NY']}}, {'answer': ['3.4.5.6'], 'meta': {'country': ['US']}}, {'answer': ['4.5.6.7'], 'meta': {'iso_region_code': ['NA-US-WA']}}, ], 'ttl': 34, }, ] nsone_zone.search = zone_search load_mock.side_effect = [nsone_zone, nsone_zone] plan = provider.plan(desired) self.assertEquals(3, len(plan.changes)) self.assertIsInstance(plan.changes[0], Update) self.assertIsInstance(plan.changes[2], Delete) # ugh, we need a mock record that can be returned from loadRecord for # the update and delete targets, we can add our side effects to that to # trigger rate limit handling mock_record = Mock() mock_record.update.side_effect = [ RateLimitException('one', period=0), None, None, ] mock_record.delete.side_effect = [ RateLimitException('two', period=0), None, None, ] nsone_zone.loadRecord.side_effect = [mock_record, mock_record, mock_record] got_n = provider.apply(plan) self.assertEquals(3, got_n) nsone_zone.loadRecord.assert_has_calls([ call('unit.tests', u'A'), call('geo', u'A'), call('delete-me', u'A'), ]) mock_record.assert_has_calls([ call.update(answers=[{'answer': [u'1.2.3.4'], 'meta': {}}], filters=[], ttl=32), call.update(answers=[{u'answer': [u'1.2.3.4'], u'meta': {}}], filters=[], ttl=32), call.update( answers=[ {u'answer': [u'101.102.103.104'], u'meta': {}}, {u'answer': [u'101.102.103.105'], u'meta': {}}, { u'answer': [u'201.202.203.204'], u'meta': { u'iso_region_code': [u'NA-US-NY'] }, }, ], filters=[ {u'filter': u'shuffle', u'config': {}}, {u'filter': u'geotarget_country', u'config': {}}, {u'filter': u'select_first_n', u'config': {u'N': 1}}, ], ttl=34), call.delete(), call.delete() ])
def test_populate(self, load_mock): provider = Ns1Provider('test', 'api-key') # Bad auth load_mock.side_effect = AuthException('unauthorized') zone = Zone('unit.tests.', []) with self.assertRaises(AuthException) as ctx: provider.populate(zone) self.assertEquals(load_mock.side_effect, ctx.exception) # General error load_mock.reset_mock() load_mock.side_effect = ResourceException('boom') zone = Zone('unit.tests.', []) with self.assertRaises(ResourceException) as ctx: provider.populate(zone) self.assertEquals(load_mock.side_effect, ctx.exception) self.assertEquals(('unit.tests',), load_mock.call_args[0]) # Non-existant zone doesn't populate anything load_mock.reset_mock() load_mock.side_effect = \ ResourceException('server error: zone not found') zone = Zone('unit.tests.', []) provider.populate(zone) self.assertEquals(set(), zone.records) self.assertEquals(('unit.tests',), load_mock.call_args[0]) # Existing zone w/o records load_mock.reset_mock() nsone_zone = DummyZone([]) load_mock.side_effect = [nsone_zone] zone_search = Mock() zone_search.return_value = [ { "domain": "geo.unit.tests", "zone": "unit.tests", "type": "A", "answers": [ {'answer': ['1.1.1.1'], 'meta': {}}, {'answer': ['1.2.3.4'], 'meta': {'ca_province': ['ON']}}, {'answer': ['2.3.4.5'], 'meta': {'us_state': ['NY']}}, {'answer': ['3.4.5.6'], 'meta': {'country': ['US']}}, {'answer': ['4.5.6.7'], 'meta': {'iso_region_code': ['NA-US-WA']}}, ], 'ttl': 34, }, ] nsone_zone.search = zone_search zone = Zone('unit.tests.', []) provider.populate(zone) self.assertEquals(1, len(zone.records)) self.assertEquals(('unit.tests',), load_mock.call_args[0]) # Existing zone w/records load_mock.reset_mock() nsone_zone = DummyZone(self.nsone_records) load_mock.side_effect = [nsone_zone] zone_search = Mock() zone_search.return_value = [ { "domain": "geo.unit.tests", "zone": "unit.tests", "type": "A", "answers": [ {'answer': ['1.1.1.1'], 'meta': {}}, {'answer': ['1.2.3.4'], 'meta': {'ca_province': ['ON']}}, {'answer': ['2.3.4.5'], 'meta': {'us_state': ['NY']}}, {'answer': ['3.4.5.6'], 'meta': {'country': ['US']}}, {'answer': ['4.5.6.7'], 'meta': {'iso_region_code': ['NA-US-WA']}}, ], 'ttl': 34, }, ] nsone_zone.search = zone_search zone = Zone('unit.tests.', []) provider.populate(zone) self.assertEquals(self.expected, zone.records) self.assertEquals(('unit.tests',), load_mock.call_args[0])
def test_sync(self, load_mock, create_mock): provider = Ns1Provider('test', 'api-key') desired = Zone('unit.tests.', []) for r in self.expected: desired.add_record(r) plan = provider.plan(desired) # everything except the root NS expected_n = len(self.expected) - 1 self.assertEquals(expected_n, len(plan.changes)) # Fails, general error load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = ResourceException('boom') with self.assertRaises(ResourceException) as ctx: provider.apply(plan) self.assertEquals(load_mock.side_effect, ctx.exception) # Fails, bad auth load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = \ ResourceException('server error: zone not found') create_mock.side_effect = AuthException('unauthorized') with self.assertRaises(AuthException) as ctx: provider.apply(plan) self.assertEquals(create_mock.side_effect, ctx.exception) # non-existant zone, create load_mock.reset_mock() create_mock.reset_mock() load_mock.side_effect = \ ResourceException('server error: zone not found') # ugh, need a mock zone with a mock prop since we're using getattr, we # can actually control side effects on `meth` with that. mock_zone = Mock() mock_zone.add_SRV = Mock() mock_zone.add_SRV.side_effect = [ RateLimitException('boo', period=0), None, ] create_mock.side_effect = [mock_zone] got_n = provider.apply(plan) self.assertEquals(expected_n, got_n) # Update & delete load_mock.reset_mock() create_mock.reset_mock() nsone_zone = DummyZone(self.nsone_records + [{ 'type': 'A', 'ttl': 42, 'short_answers': ['9.9.9.9'], 'domain': 'delete-me.unit.tests.', }]) nsone_zone.data['records'][0]['short_answers'][0] = '2.2.2.2' nsone_zone.loadRecord = Mock() load_mock.side_effect = [nsone_zone, nsone_zone] plan = provider.plan(desired) self.assertEquals(2, len(plan.changes)) self.assertIsInstance(plan.changes[0], Update) self.assertIsInstance(plan.changes[1], Delete) # ugh, we need a mock record that can be returned from loadRecord for # the update and delete targets, we can add our side effects to that to # trigger rate limit handling mock_record = Mock() mock_record.update.side_effect = [ RateLimitException('one', period=0), None, ] mock_record.delete.side_effect = [ RateLimitException('two', period=0), None, ] nsone_zone.loadRecord.side_effect = [mock_record, mock_record] got_n = provider.apply(plan) self.assertEquals(2, got_n) nsone_zone.loadRecord.assert_has_calls([ call('unit.tests', u'A'), call('delete-me', u'A'), ]) mock_record.assert_has_calls( [call.update(answers=[u'1.2.3.4'], ttl=32), call.delete()])