diff --git a/designate/tests/unit/test_mdns/__init__.py b/designate/tests/unit/mdns/__init__.py similarity index 100% rename from designate/tests/unit/test_mdns/__init__.py rename to designate/tests/unit/mdns/__init__.py diff --git a/designate/tests/unit/test_mdns/test_handler.py b/designate/tests/unit/mdns/test_handler.py similarity index 59% rename from designate/tests/unit/test_mdns/test_handler.py rename to designate/tests/unit/mdns/test_handler.py index 5f34ae5c5..91f9a7fed 100644 --- a/designate/tests/unit/test_mdns/test_handler.py +++ b/designate/tests/unit/mdns/test_handler.py @@ -13,104 +13,102 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -import unittest - -from mock import Mock -from oslo_log import log as logging import dns +import mock +import oslotest.base from designate import exceptions from designate import objects from designate.mdns import handler -LOG = logging.getLogger(__name__) - - -class TestRequestHandlerCall(unittest.TestCase): - """ - Unit test to assert the dispatching based on the request - operation. - """ +class TestRequestHandlerCall(oslotest.base.BaseTestCase): def setUp(self): - self.storage = Mock() - self.tg = Mock() - self.handler = handler.RequestHandler(self.storage, self.tg) + super(TestRequestHandlerCall, self).setUp() + self.handler = handler.RequestHandler(mock.Mock(), mock.Mock()) + self.handler._central_api = mock.Mock(name='central_api') # Use a simple handlers that doesn't require a real request - self.handler._handle_query_error = Mock(return_value='Error') - self.handler._handle_axfr = Mock(return_value=['AXFR']) - self.handler._handle_record_query = Mock(return_value=['Record Query']) - self.handler._handle_notify = Mock(return_value=['Notify']) - - def assert_error(self, request, error_type): - self.handler._handle_query_error.assert_called_with( - request, error_type - ) + self.handler._handle_query_error = mock.Mock(return_value='Error') + self.handler._handle_axfr = mock.Mock(return_value=['AXFR']) + self.handler._handle_record_query = mock.Mock( + return_value=['Record Query']) + self.handler._handle_notify = mock.Mock(return_value=['Notify']) def test_central_api_property(self): self.handler._central_api = 'foo' - assert self.handler.central_api == 'foo' + self.assertEqual(self.handler.central_api, 'foo') - def test___call___unhandled_opcodes(self): + def test__call___unhandled_opcodes(self): unhandled_codes = [ dns.opcode.STATUS, dns.opcode.IQUERY, dns.opcode.UPDATE, ] - request = Mock() + request = mock.Mock() for code in unhandled_codes: request.opcode.return_value = code # return an error - assert list(self.handler(request)) == ['Error'] - self.assert_error(request, dns.rcode.REFUSED) - def test___call__query_error_with_more_than_one_question(self): - request = Mock() + self.assertEqual(['Error'], list(self.handler(request))) + self.handler._handle_query_error.assert_called_with( + request, dns.rcode.REFUSED + ) + + def test__call__query_error_with_more_than_one_question(self): + request = mock.Mock() request.opcode.return_value = dns.opcode.QUERY - request.question = [Mock(), Mock()] + request.question = [mock.Mock(), mock.Mock()] - assert list(self.handler(request)) == ['Error'] - self.assert_error(request, dns.rcode.REFUSED) + self.assertEqual(['Error'], list(self.handler(request))) + self.handler._handle_query_error.assert_called_with( + request, dns.rcode.REFUSED + ) - def test___call__query_error_with_data_claas_not_in(self): - request = Mock() + def test__call__query_error_with_data_claas_not_in(self): + request = mock.Mock() request.opcode.return_value = dns.opcode.QUERY - request.question = [Mock(rdclass=dns.rdataclass.ANY)] - assert list(self.handler(request)) == ['Error'] - self.assert_error(request, dns.rcode.REFUSED) + request.question = [mock.Mock(rdclass=dns.rdataclass.ANY)] - def test___call__axfr(self): - request = Mock() + self.assertEqual(['Error'], list(self.handler(request))) + self.handler._handle_query_error.assert_called_with( + request, dns.rcode.REFUSED + ) + + def test__call__axfr(self): + request = mock.Mock() request.opcode.return_value = dns.opcode.QUERY request.question = [ - Mock(rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.AXFR) + mock.Mock(rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.AXFR) ] - assert list(self.handler(request)) == ['AXFR'] - def test___call__ixfr(self): - request = Mock() + self.assertEqual(['AXFR'], list(self.handler(request))) + + def test__call__ixfr(self): + request = mock.Mock() request.opcode.return_value = dns.opcode.QUERY request.question = [ - Mock(rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.IXFR) + mock.Mock(rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.IXFR) ] - assert list(self.handler(request)) == ['AXFR'] - def test___call__record_query(self): - request = Mock() + self.assertEqual(['AXFR'], list(self.handler(request))) + + def test__call__record_query(self): + request = mock.Mock() request.opcode.return_value = dns.opcode.QUERY request.question = [ - Mock(rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.A) + mock.Mock(rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.A) ] - assert list(self.handler(request)) == ['Record Query'] - def test___call__notify(self): - request = Mock() + self.assertEqual(['Record Query'], list(self.handler(request))) + + def test__call__notify(self): + request = mock.Mock() request.opcode.return_value = dns.opcode.NOTIFY - assert list(self.handler(request)) == ['Notify'] - def test__convert_to_rrset_no_records(self): + self.assertEqual(['Notify'], list(self.handler(request))) + + def test_convert_to_rrset_no_records(self): zone = objects.Zone.from_dict({'ttl': 1234}) recordset = objects.RecordSet( name='www.example.org.', @@ -120,9 +118,10 @@ class TestRequestHandlerCall(unittest.TestCase): ) r_rrset = self.handler._convert_to_rrset(zone, recordset) + self.assertIsNone(r_rrset) - def test__convert_to_rrset(self): + def test_convert_to_rrset(self): zone = objects.Zone.from_dict({'ttl': 1234}) recordset = objects.RecordSet( name='www.example.org.', @@ -134,17 +133,19 @@ class TestRequestHandlerCall(unittest.TestCase): ) r_rrset = self.handler._convert_to_rrset(zone, recordset) + self.assertEqual(2, len(r_rrset)) -class HandleRecordQueryTest(unittest.TestCase): +class HandleRecordQueryTest(oslotest.base.BaseTestCase): def setUp(self): - self.storage = Mock() - self.tg = Mock() - self.handler = handler.RequestHandler(self.storage, self.tg) + super(HandleRecordQueryTest, self).setUp() + self.context = mock.Mock() + self.storage = mock.Mock() + self.handler = handler.RequestHandler(self.storage, mock.Mock()) - def test__handle_record_query_empty_recordlist(self): + def test_handle_record_query_empty_recordlist(self): # bug #1550441 self.storage.find_recordset.return_value = objects.RecordSet( name='www.example.org.', @@ -152,15 +153,17 @@ class HandleRecordQueryTest(unittest.TestCase): records=objects.RecordList(objects=[ ]) ) + request = dns.message.make_query('www.example.org.', dns.rdatatype.A) - request.environ = dict(context='ctx') + request.environ = dict(context=self.context) response_gen = self.handler._handle_record_query(request) - for r in response_gen: + for response in response_gen: # This was raising an exception due to bug #1550441 - out = r.to_wire(max_size=65535) + out = response.to_wire(max_size=65535) + self.assertEqual(33, len(out)) - def test__handle_record_query_zone_not_found(self): + def test_handle_record_query_zone_not_found(self): self.storage.find_recordset.return_value = objects.RecordSet( name='www.example.org.', type='A', @@ -169,13 +172,15 @@ class HandleRecordQueryTest(unittest.TestCase): ]) ) self.storage.find_zone.side_effect = exceptions.ZoneNotFound + request = dns.message.make_query('www.example.org.', dns.rdatatype.A) - request.environ = dict(context='ctx') + request.environ = dict(context=self.context) response = tuple(self.handler._handle_record_query(request)) + self.assertEqual(1, len(response)) self.assertEqual(dns.rcode.REFUSED, response[0].rcode()) - def test__handle_record_query_forbidden(self): + def test_handle_record_query_forbidden(self): self.storage.find_recordset.return_value = objects.RecordSet( name='www.example.org.', type='A', @@ -184,24 +189,30 @@ class HandleRecordQueryTest(unittest.TestCase): ]) ) self.storage.find_zone.side_effect = exceptions.Forbidden + request = dns.message.make_query('www.example.org.', dns.rdatatype.A) - request.environ = dict(context='ctx') + request.environ = dict(context=self.context) response = tuple(self.handler._handle_record_query(request)) + self.assertEqual(1, len(response)) self.assertEqual(dns.rcode.REFUSED, response[0].rcode()) - def test__handle_record_query_find_recordsed_forbidden(self): + def test_handle_record_query_find_recordsed_forbidden(self): self.storage.find_recordset.side_effect = exceptions.Forbidden + request = dns.message.make_query('www.example.org.', dns.rdatatype.A) - request.environ = dict(context='ctx') + request.environ = dict(context=self.context) response = tuple(self.handler._handle_record_query(request)) + self.assertEqual(1, len(response)) self.assertEqual(dns.rcode.REFUSED, response[0].rcode()) - def test__handle_record_query_find_recordsed_not_found(self): + def test_handle_record_query_find_recordsed_not_found(self): self.storage.find_recordset.side_effect = exceptions.NotFound + request = dns.message.make_query('www.example.org.', dns.rdatatype.A) - request.environ = dict(context='ctx') + request.environ = dict(context=self.context) response = tuple(self.handler._handle_record_query(request)) + self.assertEqual(1, len(response)) self.assertEqual(dns.rcode.REFUSED, response[0].rcode()) diff --git a/designate/tests/unit/mdns/test_notify.py b/designate/tests/unit/mdns/test_notify.py new file mode 100644 index 000000000..6174ac083 --- /dev/null +++ b/designate/tests/unit/mdns/test_notify.py @@ -0,0 +1,307 @@ +# Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# Author: Federico Ceratto +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import socket + +import dns +import dns.rdataclass +import dns.rdatatype +import mock + +import designate.mdns.base as mdnsbase +import designate.mdns.notify as notify +import designate.tests +from designate.tests.unit import RoObject + + +class MdnsNotifyTest(designate.tests.TestCase): + def setUp(self): + super(MdnsNotifyTest, self).setUp() + + self.notify = notify.NotifyEndpoint(mock.Mock()) + + @mock.patch('time.sleep') + def test_notify_zone_changed(self, mock_sleep): + self.notify._make_and_send_dns_message = mock.Mock() + + self.notify.notify_zone_changed(*range(8)) + + self.notify._make_and_send_dns_message.assert_called_with( + 1, 2, 3, 4, 5, 6, notify=True + ) + + @mock.patch.object(mdnsbase.pool_mngr_api.PoolManagerAPI, 'get_instance') + def test_poll_for_serial_number(self, mock_get_instance): + self.notify.get_serial_number = mock.Mock( + return_value=('status', 99, 9) + ) + ns = RoObject(host='host', port=1234) + + self.notify.poll_for_serial_number( + 'c', 'z', ns, 1, 2, 3, 4 + ) + + self.notify.get_serial_number.assert_called_with( + 'c', 'z', 'host', 1234, 1, 2, 3, 4 + ) + self.notify.pool_manager_api.update_status.assert_called_with( + 'c', 'z', ns, 'status', 99 + ) + + @mock.patch('time.sleep') + def test_get_serial_number_nxdomain(self, mock_sleep): + # The zone is not found but it was supposed to be there + response = RoObject( + answer=[RoObject( + rdclass=dns.rdataclass.IN, + rdtype=dns.rdatatype.SOA + )], + rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN) + ) + zone = RoObject(name='zn', serial=314) + self.notify._make_and_send_dns_message = mock.Mock( + return_value=(response, 1) + ) + + out = self.notify.get_serial_number( + 'c', zone, 'h', 1234, 1, 2, 3, 4 + ) + + self.assertEqual(('NO_ZONE', None, 0), out) + + @mock.patch('time.sleep') + def test_get_serial_number_nxdomain_deleted_zone(self, mock_sleep): + # The zone is not found and it's not was supposed be there + response = RoObject( + answer=[RoObject( + rdclass=dns.rdataclass.IN, + rdtype=dns.rdatatype.SOA + )], + rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN) + ) + zone = RoObject(name='zn', serial=0, action='DELETE') + self.notify._make_and_send_dns_message = mock.Mock( + return_value=(response, 1) + ) + + out = self.notify.get_serial_number( + 'c', zone, 'h', 1234, 1, 2, 3, 4 + ) + + self.assertEqual(('NO_ZONE', 0, 3), out) + + @mock.patch('time.sleep') + def test_get_serial_number_ok(self, mock_sleep): + zone = RoObject(name='zn', serial=314) + ds = RoObject(items=[zone]) + response = RoObject( + answer=[RoObject( + name='zn', + rdclass=dns.rdataclass.IN, + rdtype=dns.rdatatype.SOA, + to_rdataset=mock.Mock(return_value=ds) + )], + rcode=mock.Mock(return_value=dns.rcode.NOERROR) + ) + self.notify._make_and_send_dns_message = mock.Mock( + return_value=(response, 1) + ) + + out = self.notify.get_serial_number( + 'c', zone, 'h', 1234, 1, 2, 3, 4 + ) + + self.assertEqual(('SUCCESS', 314, 3), out) + + @mock.patch('time.sleep') + def test_get_serial_number_too_many_retries(self, mock_sleep): + zone = RoObject(name='zn', serial=314) + ds = RoObject(items=[RoObject(serial=310)]) + response = RoObject( + answer=[RoObject( + name='zn', + rdclass=dns.rdataclass.IN, + rdtype=dns.rdatatype.SOA, + to_rdataset=mock.Mock(return_value=ds) + )], + rcode=mock.Mock(return_value=dns.rcode.NOERROR) + ) + self.notify._make_and_send_dns_message = mock.Mock( + return_value=(response, 1) + ) + + out = self.notify.get_serial_number( + 'c', zone, 'h', 1234, 1, 2, 3, 4 + ) + + self.assertEqual(('ERROR', 310, 0), out) + + @mock.patch('time.sleep') + def test_make_and_send_dns_message_timeout(self, mock_sleep): + zone = RoObject(name='zn') + self.notify._make_dns_message = mock.Mock(return_value='') + self.notify._send_dns_message = mock.Mock( + side_effect=dns.exception.Timeout + ) + + out = self.notify._make_and_send_dns_message( + zone, 'host', 123, 1, 2, 3 + ) + + self.assertEqual((None, 3), out) + + def test_make_and_send_dns_message_bad_response(self): + zone = RoObject(name='zn') + self.notify._make_dns_message = mock.Mock(return_value='') + self.notify._send_dns_message = mock.Mock( + side_effect=notify.dns_query.BadResponse + ) + + out = self.notify._make_and_send_dns_message( + zone, 'host', 123, 1, 2, 3 + ) + + self.assertEqual((None, 1), out) + + @mock.patch('time.sleep') + def test_make_and_send_dns_message_eagain(self, mock_sleep): + # bug #1558096 + zone = RoObject(name='zn') + self.notify._make_dns_message = mock.Mock(return_value='') + socket_error = socket.error() + socket_error.errno = socket.errno.EAGAIN + self.notify._send_dns_message = mock.Mock( + side_effect=socket_error + ) + + out = self.notify._make_and_send_dns_message( + zone, 'host', 123, 1, 2, 3 + ) + + self.assertEqual((None, 3), out) + + def test_make_and_send_dns_message_econnrefused(self): + # bug #1558096 + zone = RoObject(name='zn') + self.notify._make_dns_message = mock.Mock(return_value='') + socket_error = socket.error() + socket_error.errno = socket.errno.ECONNREFUSED + # socket errors other than EAGAIN should raise + self.notify._send_dns_message = mock.Mock( + side_effect=socket_error) + + self.assertRaises( + socket.error, + self.notify._make_and_send_dns_message, + zone, 'host', 123, 1, 2, 3 + ) + + def test_make_and_send_dns_message_nxdomain(self): + zone = RoObject(name='zn') + self.notify._make_dns_message = mock.Mock(return_value='') + response = RoObject(rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN)) + self.notify._send_dns_message = mock.Mock(return_value=response) + + out = self.notify._make_and_send_dns_message( + zone, 'host', 123, 1, 2, 3 + ) + + self.assertEqual((response, 1), out) + + def test_make_and_send_dns_message_missing_AA_flags(self): + zone = RoObject(name='zn') + self.notify._make_dns_message = mock.Mock(return_value='') + + response = RoObject( + rcode=mock.Mock(return_value=dns.rcode.NOERROR), + # rcode is NOERROR but (flags & dns.flags.AA) gives 0 + flags=0, + answer=['answer'], + ) + self.notify._send_dns_message = mock.Mock(return_value=response) + + out = self.notify._make_and_send_dns_message( + zone, 'host', 123, 1, 2, 3 + ) + + self.assertEqual((None, 1), out) + + def test_make_and_send_dns_message_error_flags(self): + zone = RoObject(name='zn') + self.notify._make_dns_message = mock.Mock(return_value='') + response = RoObject( + rcode=mock.Mock(return_value=dns.rcode.NOERROR), + # rcode is NOERROR but flags are not NOERROR + flags=123, + ednsflags=321, + answer=['answer'], + ) + self.notify._send_dns_message = mock.Mock(return_value=response) + + out = self.notify._make_and_send_dns_message( + zone, 'host', 123, 1, 2, 3 + ) + + self.assertEqual((None, 1), out) + + def test_make_dns_message(self): + msg = self.notify._make_dns_message('zone_name') + txt = msg.to_text().split('\n')[1:] + + self.assertEqual([ + 'opcode QUERY', + 'rcode NOERROR', + 'flags RD', + ';QUESTION', + 'zone_name. IN SOA', + ';ANSWER', + ';AUTHORITY', + ';ADDITIONAL' + ], txt) + + def test_make_dns_message_notify(self): + msg = self.notify._make_dns_message('zone_name', notify=True) + txt = msg.to_text().split('\n')[1:] + + self.assertEqual([ + 'opcode NOTIFY', + 'rcode NOERROR', + 'flags AA', + ';QUESTION', + 'zone_name. IN SOA', + ';ANSWER', + ';AUTHORITY', + ';ADDITIONAL', + ], txt) + + @mock.patch.object(notify.dns_query, 'udp') + def test_send_udp_dns_message(self, mock_udp): + self.CONF.set_override('all_tcp', False, 'service:mdns') + + self.notify._send_dns_message('msg', '192.0.2.1', 1234, 1) + + mock_udp.assert_called_with( + 'msg', '192.0.2.1', port=1234, timeout=1 + ) + + @mock.patch.object(notify.dns_query, 'tcp') + def test_send_tcp_dns_message(self, mock_tcp): + self.CONF.set_override('all_tcp', True, 'service:mdns') + + self.notify._send_dns_message('msg', '192.0.2.1', 1234, 1) + + mock_tcp.assert_called_with( + 'msg', '192.0.2.1', port=1234, timeout=1 + ) diff --git a/designate/tests/unit/test_mdns/test_service.py b/designate/tests/unit/mdns/test_service.py similarity index 52% rename from designate/tests/unit/test_mdns/test_service.py rename to designate/tests/unit/mdns/test_service.py index 9b4c44250..61e05e52e 100644 --- a/designate/tests/unit/test_mdns/test_service.py +++ b/designate/tests/unit/mdns/test_service.py @@ -13,65 +13,60 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - - -"""Unit-test MiniDNS service -""" -from oslotest import base import mock +import oslotest.base +import designate.dnsutils import designate.rpc -import designate.mdns.service as mdns -import designate.storage.base as storage +import designate.service +import designate.storage.base +import designate.utils +from designate.mdns import handler +from designate.mdns import service -class MdnsServiceTest(base.BaseTestCase): - @mock.patch.object(mdns.service.DNSService, '_start') +class MdnsServiceTest(oslotest.base.BaseTestCase): @mock.patch.object(designate.rpc, 'get_server') - def test_service_start(self, mock_service_start, mock_rpc_server): - self.mdns = mdns.Service() - self.mdns.start() + def setUp(self, mock_rpc_server): + super(MdnsServiceTest, self).setUp() + + self.service = service.Service() + + @mock.patch.object(designate.service.DNSService, '_start') + def test_service_start(self, mock_service_start): + self.service.start() self.assertTrue(mock_service_start.called) - self.assertTrue(mock_rpc_server.called) def test_service_name(self): - self.mdns = mdns.Service() - - self.assertEqual('mdns', self.mdns.service_name) + self.assertEqual('mdns', self.service.service_name) def test_rpc_endpoints(self): - self.mdns = mdns.Service() + endpoints = self.service._rpc_endpoints - endpoints = self.mdns._rpc_endpoints + self.assertIsInstance(endpoints[0], service.notify.NotifyEndpoint) + self.assertIsInstance(endpoints[1], service.xfr.XfrEndpoint) - self.assertIsInstance(endpoints[0], mdns.notify.NotifyEndpoint) - self.assertIsInstance(endpoints[1], mdns.xfr.XfrEndpoint) - - @mock.patch.object(storage.Storage, 'get_driver') + @mock.patch.object(designate.storage.base.Storage, 'get_driver') def test_storage_driver(self, mock_get_driver): mock_driver = mock.MagicMock() mock_driver.name = 'noop_driver' mock_get_driver.return_value = mock_driver - self.mdns = mdns.Service() - - self.assertIsInstance(self.mdns.storage, mock.MagicMock) + self.assertIsInstance(self.service.storage, mock.MagicMock) self.assertTrue(mock_get_driver.called) - @mock.patch.object(mdns.handler, 'RequestHandler', name='reqh') - @mock.patch.object(mdns.service.DNSService, '_start') - @mock.patch.object(mdns.utils, 'cache_result') - @mock.patch.object(storage.Storage, 'get_driver') + @mock.patch.object(handler, 'RequestHandler', name='reqh') + @mock.patch.object(designate.service.DNSService, '_start') + @mock.patch.object(designate.utils, 'cache_result') + @mock.patch.object(designate.storage.base.Storage, 'get_driver') def test_dns_application(self, mock_req_handler, mock_cache_result, mock_service_start, mock_get_driver): mock_driver = mock.MagicMock() mock_driver.name = 'noop_driver' mock_get_driver.return_value = mock_driver - self.mdns = mdns.Service() + app = self.service._dns_application - app = self.mdns._dns_application - - self.assertIsInstance(app, mdns.dnsutils.DNSMiddleware) + self.assertIsInstance(app, designate.dnsutils.DNSMiddleware) diff --git a/designate/tests/unit/test_mdns/test_notify.py b/designate/tests/unit/test_mdns/test_notify.py deleted file mode 100644 index e9c81a558..000000000 --- a/designate/tests/unit/test_mdns/test_notify.py +++ /dev/null @@ -1,274 +0,0 @@ -# Copyright 2015 Hewlett-Packard Development Company, L.P. -# -# Author: Federico Ceratto -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -"""Unit-test MiniDNS service -""" - -import socket - -from mock import Mock -from oslotest import base -import dns -import dns.rdataclass -import dns.rdatatype -import mock - -from designate.tests.unit import RoObject -import designate.mdns.notify as notify -import designate.mdns.base as mdnsbase - - -@mock.patch.object(notify.time, 'sleep') -@mock.patch.object(mdnsbase.pool_mngr_api.PoolManagerAPI, 'get_instance') -class MdnsNotifyTest(base.BaseTestCase): - - @mock.patch.object(mdnsbase.central_api.CentralAPI, 'get_instance') - def setUp(self, *mocks): - super(MdnsNotifyTest, self).setUp() - notify.CONF = RoObject({ - 'service:mdns': RoObject(all_tcp=False) - }) - self.tg = Mock(name='tg') - self.notify = notify.NotifyEndpoint(self.tg) - - def test_notify_zone_changed(self, *mocks): - self.notify._make_and_send_dns_message = Mock() - - self.notify.notify_zone_changed(*range(8)) - - self.notify._make_and_send_dns_message.assert_called_with( - 1, 2, 3, 4, 5, 6, notify=True) - - def test_poll_for_serial_number(self, *mocks): - self.notify.get_serial_number = Mock( - return_value=('status', 99, 9) - ) - ns = RoObject(host='host', port=1234) - - self.notify.poll_for_serial_number('c', 'z', ns, 1, 2, 3, 4) - - self.notify.get_serial_number.assert_called_with( - 'c', 'z', 'host', 1234, 1, 2, 3, 4) - self.notify.pool_manager_api.update_status.assert_called_with( - 'c', 'z', ns, 'status', 99) - - def test_get_serial_number_nxdomain(self, *mocks): - # The zone is not found but it was supposed to be there - response = RoObject( - answer=[RoObject( - rdclass=dns.rdataclass.IN, - rdtype=dns.rdatatype.SOA - )], - rcode=Mock(return_value=dns.rcode.NXDOMAIN) - ) - zone = RoObject(name='zn', serial=314) - self.notify._make_and_send_dns_message = Mock( - return_value=(response, 1) - ) - - out = self.notify.get_serial_number('c', zone, 'h', 1234, 1, 2, 3, 4) - - self.assertEqual(('NO_ZONE', None, 0), out) - - def test_get_serial_number_nxdomain_deleted_zone(self, *mocks): - # The zone is not found and it's not was supposed be there - response = RoObject( - answer=[RoObject( - rdclass=dns.rdataclass.IN, - rdtype=dns.rdatatype.SOA - )], - rcode=Mock(return_value=dns.rcode.NXDOMAIN) - ) - zone = RoObject(name='zn', serial=0, action='DELETE') - self.notify._make_and_send_dns_message = Mock( - return_value=(response, 1) - ) - - out = self.notify.get_serial_number('c', zone, 'h', 1234, 1, 2, 3, 4) - - self.assertEqual(('NO_ZONE', 0, 3), out) - - def test_get_serial_number_ok(self, *mocks): - zone = RoObject(name='zn', serial=314) - ds = RoObject(items=[zone]) - response = RoObject( - answer=[RoObject( - name='zn', - rdclass=dns.rdataclass.IN, - rdtype=dns.rdatatype.SOA, - to_rdataset=Mock(return_value=ds) - )], - rcode=Mock(return_value=dns.rcode.NOERROR) - ) - self.notify._make_and_send_dns_message = Mock( - return_value=(response, 1) - ) - - out = self.notify.get_serial_number('c', zone, 'h', 1234, 1, 2, 3, 4) - - self.assertEqual(('SUCCESS', 314, 3), out) - - def test_get_serial_number_too_many_retries(self, *mocks): - zone = RoObject(name='zn', serial=314) - ds = RoObject(items=[RoObject(serial=310)]) - response = RoObject( - answer=[RoObject( - name='zn', - rdclass=dns.rdataclass.IN, - rdtype=dns.rdatatype.SOA, - to_rdataset=Mock(return_value=ds) - )], - rcode=Mock(return_value=dns.rcode.NOERROR) - ) - self.notify._make_and_send_dns_message = Mock( - return_value=(response, 1) - ) - - out = self.notify.get_serial_number('c', zone, 'h', 1234, 1, 2, 3, 4) - - self.assertEqual(('ERROR', 310, 0), out) - - def test_make_and_send_dns_message_timeout(self, *mocks): - zone = RoObject(name='zn') - self.notify._make_dns_message = Mock(return_value='') - self.notify._send_dns_message = Mock( - side_effect=dns.exception.Timeout) - - out = self.notify._make_and_send_dns_message(zone, 'host', - 123, 1, 2, 3) - - self.assertEqual((None, 3), out) - - def test_make_and_send_dns_message_bad_response(self, *mocks): - zone = RoObject(name='zn') - self.notify._make_dns_message = Mock(return_value='') - self.notify._send_dns_message = Mock( - side_effect=notify.dns_query.BadResponse) - - out = self.notify._make_and_send_dns_message(zone, 'host', - 123, 1, 2, 3) - - self.assertEqual((None, 1), out) - - def test_make_and_send_dns_message_eagain(self, *mocks): - # bug #1558096 - zone = RoObject(name='zn') - self.notify._make_dns_message = Mock(return_value='') - socket_error = socket.error() - socket_error.errno = socket.errno.EAGAIN - self.notify._send_dns_message = Mock( - side_effect=socket_error) - - out = self.notify._make_and_send_dns_message(zone, 'host', - 123, 1, 2, 3) - - self.assertEqual((None, 3), out) - - def test_make_and_send_dns_message_econnrefused(self, *mocks): - # bug #1558096 - zone = RoObject(name='zn') - self.notify._make_dns_message = Mock(return_value='') - socket_error = socket.error() - socket_error.errno = socket.errno.ECONNREFUSED - # socket errors other than EAGAIN should raise - self.notify._send_dns_message = Mock( - side_effect=socket_error) - - self.assertRaises(socket.error, self.notify._make_and_send_dns_message, - zone, 'host', 123, 1, 2, 3) - - def test_make_and_send_dns_message_nxdomain(self, *mocks): - zone = RoObject(name='zn') - self.notify._make_dns_message = Mock(return_value='') - response = RoObject(rcode=Mock(return_value=dns.rcode.NXDOMAIN)) - self.notify._send_dns_message = Mock(return_value=response) - - out = self.notify._make_and_send_dns_message(zone, 'host', - 123, 1, 2, 3) - - self.assertEqual((response, 1), out) - - def test_make_and_send_dns_message_missing_AA_flags(self, *mocks): - zone = RoObject(name='zn') - self.notify._make_dns_message = Mock(return_value='') - response = RoObject( - rcode=Mock(return_value=dns.rcode.NOERROR), - # rcode is NOERROR but (flags & dns.flags.AA) gives 0 - flags=0, - answer=['answer'], - ) - self.notify._send_dns_message = Mock(return_value=response) - - out = self.notify._make_and_send_dns_message(zone, 'host', - 123, 1, 2, 3) - - self.assertEqual((None, 1), out) - - def test_make_and_send_dns_message_error_flags(self, *mocks): - zone = RoObject(name='zn') - self.notify._make_dns_message = Mock(return_value='') - response = RoObject( - rcode=Mock(return_value=dns.rcode.NOERROR), - # rcode is NOERROR but flags are not NOERROR - flags=123, - ednsflags=321, - answer=['answer'], - ) - self.notify._send_dns_message = Mock(return_value=response) - - out = self.notify._make_and_send_dns_message(zone, 'host', - 123, 1, 2, 3) - - self.assertEqual((None, 1), out) - - def test_make_dns_message(self, *mocks): - msg = self.notify._make_dns_message('zone_name') - txt = msg.to_text().split('\n')[1:] - self.assertEqual([ - 'opcode QUERY', - 'rcode NOERROR', - 'flags RD', - ';QUESTION', - 'zone_name. IN SOA', - ';ANSWER', - ';AUTHORITY', - ';ADDITIONAL' - ], txt) - - def test_make_dns_message_notify(self, *mocks): - msg = self.notify._make_dns_message('zone_name', notify=True) - txt = msg.to_text().split('\n')[1:] - self.assertEqual([ - 'opcode NOTIFY', - 'rcode NOERROR', - 'flags AA', - ';QUESTION', - 'zone_name. IN SOA', - ';ANSWER', - ';AUTHORITY', - ';ADDITIONAL', - ], txt) - - @mock.patch.object(notify.dns_query, 'tcp') - @mock.patch.object(notify.dns_query, 'udp') - def test_send_dns_message(self, *mocks): - out = self.notify._send_dns_message('msg', '192.0.2.1', 1234, 1) - - assert not notify.dns_query.tcp.called - notify.dns_query.udp.assert_called_with('msg', '192.0.2.1', port=1234, - timeout=1) - assert isinstance(out, Mock)