From 95f451dc8a07a0434210be5629490f150bf2962e Mon Sep 17 00:00:00 2001 From: Alin Balutoiu Date: Wed, 22 Jun 2016 09:34:28 +0300 Subject: [PATCH] Add support for Microsoft DNS Server backend Currently, there is no support for Microsoft DNS in designate. This patch addresses this issue by adding support for Microsoft DNS Server using an agent backend in designate. Change-Id: I8db1906e17e5fb20fa6f3e5d1f13b2d701f0c032 Implements: blueprint msdns-backend-support Depends-On: I029747555a58e0a8e362b65e6c0c470cf2774e42 --- designate/backend/agent_backend/impl_msdns.py | 113 +++++++++++++++ .../test_agent/test_backends/test_msdns.py | 122 ++++++++++++++++ .../designate_plugins/backend-agent-msdns | 116 +++++++++++++++ doc/source/backends/msdns_agent.rst | 134 ++++++++++++++++++ doc/source/sourcedoc/backend.rst | 10 ++ doc/source/support-matrix.ini | 5 + etc/designate/designate.conf.sample | 3 + requirements.txt | 1 + setup.cfg | 1 + 9 files changed, 505 insertions(+) create mode 100644 designate/backend/agent_backend/impl_msdns.py create mode 100644 designate/tests/unit/test_agent/test_backends/test_msdns.py create mode 100644 devstack/designate_plugins/backend-agent-msdns create mode 100644 doc/source/backends/msdns_agent.rst diff --git a/designate/backend/agent_backend/impl_msdns.py b/designate/backend/agent_backend/impl_msdns.py new file mode 100644 index 000000000..949342fa2 --- /dev/null +++ b/designate/backend/agent_backend/impl_msdns.py @@ -0,0 +1,113 @@ +# Copyright 2016 Cloudbase Solutions Srl +# All Rights Reserved. +# +# Author: Alin Balutoiu +# +# 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. + +from oslo_config import cfg +from oslo_log import log as logging +from os_win import utilsfactory +from os_win import constants +from os_win import exceptions as os_win_exc + +from designate.backend.agent_backend import base +from designate import exceptions +from designate.i18n import _LI + + +LOG = logging.getLogger(__name__) +CFG_GROUP = 'backend:agent:msdns' + +GROUP = cfg.OptGroup( + name=CFG_GROUP, + title="Configuration for Microsoft DNS Server" +) +OPTS = [] + + +class MSDNSBackend(base.AgentBackend): + + __plugin_name__ = 'msdns' + __backend_status__ = 'experimental' + + def __init__(self, agent_service): + """Configure the backend""" + super(MSDNSBackend, self).__init__(agent_service) + + self._dnsutils = utilsfactory.get_dnsutils() + + masters = cfg.CONF['service:agent'].masters + if not masters: + raise exceptions.Backend("Missing agent AXFR masters") + # Only ip addresses are needed + self._masters = [ns.split(":")[0] for ns in masters] + + LOG.info(_LI("AXFR masters: %r"), self._masters) + + @classmethod + def get_cfg_opts(cls): + return [(GROUP, OPTS)] + + def start(self): + """Start the backend""" + LOG.info(_LI("Started msdns backend")) + + def find_zone_serial(self, zone_name): + """Return the zone's serial""" + zone_name = zone_name.rstrip(".") + LOG.debug("Finding zone: %s" % zone_name) + try: + return self._dnsutils.get_zone_serial(zone_name) + except os_win_exc.DNSZoneNotFound: + # Return None if the zone was not found + return None + + def create_zone(self, zone): + """Create a new DNS Zone""" + zone_name = zone.origin.to_text(omit_final_dot=True) + LOG.debug("Creating zone: %s" % zone_name) + try: + self._dnsutils.zone_create( + zone_name=zone_name, + zone_type=constants.DNS_ZONE_TYPE_SECONDARY, + ds_integrated=False, + ip_addrs=self._masters) + except os_win_exc.DNSZoneAlreadyExists: + # Zone already exists, check its properties to see if the + # existing zone is identical to the requested one + zone_properties = self._dnsutils.get_zone_properties(zone_name) + + identical_zone_exists = ( + zone_properties['zone_type'] == ( + constants.DNS_ZONE_TYPE_SECONDARY) and + zone_properties['ds_integrated'] is False and + set(zone_properties['master_servers']) == set(self._masters)) + + if not identical_zone_exists: + raise + + def update_zone(self, zone): + """Instruct MSDNS to request an AXFR from MiniDNS. + """ + zone_name = zone.origin.to_text(omit_final_dot=True) + LOG.debug("Updating zone: %s" % zone_name) + self._dnsutils.zone_update(zone_name) + + def delete_zone(self, zone_name): + """Delete a DNS Zone + Do not raise exception if the zone does not exist. + """ + LOG.debug('Deleting zone: %s' % zone_name) + zone_name = zone_name.rstrip(".") + self._dnsutils.zone_delete(zone_name) diff --git a/designate/tests/unit/test_agent/test_backends/test_msdns.py b/designate/tests/unit/test_agent/test_backends/test_msdns.py new file mode 100644 index 000000000..ed798e5e6 --- /dev/null +++ b/designate/tests/unit/test_agent/test_backends/test_msdns.py @@ -0,0 +1,122 @@ +# Copyright 2016 Cloudbase Solutions Srl +# All Rights Reserved. +# +# Author: Alin Balutoiu +# +# 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 the MSDNS agent backend +""" + +import dns +import mock +from os_win import constants +from os_win import exceptions as os_win_exc + +from designate.backend.agent_backend import impl_msdns +from designate.tests import TestCase + + +class MSDNSAgentBackendUnitTestCase(TestCase): + + _FAKE_ZONE_NAME = 'example.com' + + def setUp(self): + super(MSDNSAgentBackendUnitTestCase, self).setUp() + self.CONF.set_override('masters', ('127.0.0.1:5354',), 'service:agent') + + patcher = mock.patch('os_win.utilsfactory.get_dnsutils') + self._dnsutils = patcher.start().return_value + self.addCleanup(patcher.stop) + self.backend = impl_msdns.MSDNSBackend('foo') + + def _create_dnspy_zone(self, name): + zone_text = ( + '$ORIGIN %(name)s\n%(name)s 3600 IN SOA %(ns)s ' + 'email.email.com. 1421777854 3600 600 86400 3600\n%(name)s ' + '3600 IN NS %(ns)s\n') % {'name': name, 'ns': 'ns1.designate.com'} + + return dns.zone.from_text(zone_text, check_origin=False) + + def test_init(self): + self.assertEqual(['127.0.0.1'], self.backend._masters) + self.assertEqual(self._dnsutils, self.backend._dnsutils) + + def test_find_zone_serial(self): + serial = self.backend.find_zone_serial(self._FAKE_ZONE_NAME) + + expected_serial = self._dnsutils.get_zone_serial.return_value + self.assertEqual(expected_serial, serial) + self._dnsutils.get_zone_serial.assert_called_once_with( + self._FAKE_ZONE_NAME) + + def test_find_zone_serial_error(self): + self._dnsutils.get_zone_serial.side_effect = ( + os_win_exc.DNSZoneNotFound(zone_name=self._FAKE_ZONE_NAME)) + + serial = self.backend.find_zone_serial(self._FAKE_ZONE_NAME) + + self.assertIsNone(serial) + self._dnsutils.get_zone_serial.assert_called_once_with( + self._FAKE_ZONE_NAME) + + def test_create_zone(self): + zone = self._create_dnspy_zone(self._FAKE_ZONE_NAME) + + self.backend.create_zone(zone) + + self._dnsutils.zone_create.assert_called_once_with( + zone_name=self._FAKE_ZONE_NAME, + zone_type=constants.DNS_ZONE_TYPE_SECONDARY, + ds_integrated=False, + ip_addrs=self.backend._masters) + + def test_create_zone_already_existing_diff(self): + zone = self._create_dnspy_zone(self._FAKE_ZONE_NAME) + self._dnsutils.zone_create.side_effect = ( + os_win_exc.DNSZoneAlreadyExists(zone_name=self._FAKE_ZONE_NAME)) + + self.assertRaises(os_win_exc.DNSZoneAlreadyExists, + self.backend.create_zone, + zone) + self._dnsutils.get_zone_properties.assert_called_once_with( + self._FAKE_ZONE_NAME) + + def test_create_zone_already_existing_identical(self): + zone = self._create_dnspy_zone(self._FAKE_ZONE_NAME) + self._dnsutils.zone_create.side_effect = ( + os_win_exc.DNSZoneAlreadyExists(zone_name=self._FAKE_ZONE_NAME)) + mock_zone_properties = {'zone_type': constants.DNS_ZONE_TYPE_SECONDARY, + 'ds_integrated': False, + 'master_servers': self.backend._masters} + self._dnsutils.get_zone_properties.return_value = mock_zone_properties + + self.backend.create_zone(zone) + + self._dnsutils.get_zone_properties.assert_called_once_with( + self._FAKE_ZONE_NAME) + + def test_update_zone(self): + zone = self._create_dnspy_zone(self._FAKE_ZONE_NAME) + + self.backend.update_zone(zone) + + self._dnsutils.zone_update.assert_called_once_with( + self._FAKE_ZONE_NAME) + + def test_delete_zone(self): + self.backend.delete_zone(self._FAKE_ZONE_NAME) + + self._dnsutils.zone_delete.assert_called_once_with( + self._FAKE_ZONE_NAME) diff --git a/devstack/designate_plugins/backend-agent-msdns b/devstack/designate_plugins/backend-agent-msdns new file mode 100644 index 000000000..1d459cef4 --- /dev/null +++ b/devstack/designate_plugins/backend-agent-msdns @@ -0,0 +1,116 @@ +# Configure the agent backend + +# Enable this pluging by adding these line to local.conf: +# +# DESIGNATE_BACKEND_DRIVER=agent +# DESIGNATE_AGENT_BACKEND_DRIVER=msdns + +# Dependencies: +# ``functions`` file +# ``designate`` configuration + +# install_designate_agent_backend - install any external requirements +# configure_designate_agent_backend - make configuration changes, including those to other services +# init_designate_agent_backend - initialize databases, etc. +# start_designate_agent_backend - start any external services +# stop_designate_agent_backend - stop any external services +# cleanup_designate_agent_backend - remove transient data and cache + +# Save trace setting +DP_AGENT_MSDNS_XTRACE=$(set +o | grep xtrace) +set +o xtrace + +# Defaults +# -------- + +DESIGNATE_MSDNS_MASTERS=${DESIGNATE_MSDNS_MASTERS:-"$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_MDNS"} +DESIGNATE_MSDNS_HOST_IP=${DESIGNATE_MSDNS_HOST_IP:-} +DESIGNATE_MSDNS_HOST_PORT=${DESIGNATE_MSDNS_HOST_PORT:-} + +# Sanity Checks +# ------------- +if [ -z "$DESIGNATE_MSDNS_MASTERS" ]; then + die $LINENO "You must configure DESIGNATE_MSDNS_MASTERS" +fi + +if [ -z "$DESIGNATE_MSDNS_HOST_IP" ]; then + die $LINENO "You must configure DESIGNATE_MSDNS_HOST_IP with the IP of the MS DNS host" +fi + +if [ -z "$DESIGNATE_MSDNS_HOST_PORT" ]; then + die $LINENO "You must configure DESIGNATE_MSDNS_HOST_PORT with the PORT of the MS DNS host" +fi + +if [ "$DESIGNATE_SERVICE_PORT_MDNS" != "53" ]; then + die $LINENO "Microsoft DNS requires DESIGNATE_SERVICE_PORT_MDNS to be set to '53'" +fi + +# Entry Points +# ------------ + +# install_designate_agent_backend - install any external requirements +function install_designate_agent_backend { + : +} + +# configure_designate_agent_backend - make configuration changes, including those to other services +function configure_designate_agent_backend { + # Generate Designate pool.yaml file + sudo tee $DESIGNATE_CONF_DIR/pools.yaml > /dev/null < + + 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. + +MSDNS Agent Backend +******************* + +MSDNS User Documentation +======================== + +This page documents using the MSDNS Agent backend. + +The agent runs on the Windows host where the Microsoft DNS Server feature +is installed. It receives DNS messages from Mini DNS using private +DNS OPCODEs and classes and creates or deletes zones using WMI calls. + +It also instructs MSDNS to request AXFR from MiniDNS when a zone is created +or updated. + +`Microsoft DNS documentation for managing DNS zones +`_ + +Setting up the Microsoft DNS server on Windows Server +----------------------------------------------------- + +The DNS Server role can be installed on the system by following the +documentation available here: +`How to install the DNS Server role +`_ + +Configuring MSDNS +----------------- + +Assuming the DNS Server role has been installed on the system, follow the +next steps to complete the configuration. + +These steps are for the Windows host which will run the designate agent. +Make sure that Python 2.7 or Python 3.4 is installed on the system already. + +To install Designate, clone the repository from https://github.com/openstack/designate +and do a pip install. Example: + +.. code-block:: powershell + + git clone https://github.com/openstack/designate + pip install .\\designate + +After that, we need to configure the Designate Agent. Inside the github repository, +there is a folder named "etc/designate" which can be used as default configuration. + +Copy the folder somewhere else, for this example we will copy it to C:\\etc\\designate +Inside the configuration folder, make a copy of designate.conf.sample and rename +the copy to designate.conf +Example: + +.. code-block:: powershell + + copy C:\\etc\\designate\\designate.conf.sample C:\\etc\\designate\\designate.conf + + +Configure the "service.agent" and "backend.agent.msdns" sections in +C:\\etc\\designate\\designate.conf + +Look in C:\\etc\\designate\\designate.conf.example for more complete examples. + +.. code-block:: ini + + [service:agent] + backend_driver = msdns + # Place here the MiniDNS ipaddr and port (no the agent itself) + masters = :53 + +Ensure that "policy_file" under the [default] section is set: + +.. code-block:: ini + + policy_file = C:\\etc\\designate\\policy.json + +Start the designate agent using (Python 2.7 was installed in the default location C:\\Python27): + +.. code-block:: powershell + + C:\\Python27\\Scripts\\designate-agent.exe --config-file 'C:\\etc\\designate\\designate.conf' + +You should see log messages similar to: + +.. code-block:: powershell + + 2016-06-22 02:00:47.177 3436 INFO designate.backend.agent_backend.impl_msdns [-] Started msdns backend + 2016-06-22 02:00:47.177 3436 INFO designate.service [-] _handle_tcp thread started + 2016-06-22 02:00:47.177 3436 INFO designate.service [-] _handle_udp thread started + + +The following steps are for the system running the Designate controller. + +Make sure to set the mDNS port to 53 in the ``[service:mdns]`` section. +MS DNS does not support Masters that are on any port other than 53. + +Create an agent pool: + +.. code-block:: bash + + # Fetch the existing pool(s) if needed or start from scratch + designate-manage pool generate_file --file /tmp/pool.yaml + # Edit the file (see below) and reload it as: + designate-manage pool update --file /tmp/pool.yaml + +The "targets" section in pool.yaml should look like: + +.. code-block:: ini + + targets: + - description: Microsoft DNS agent + masters: + - host: + port: 53 + options: {} + options: + - host: + port: 5358 + type: agent diff --git a/doc/source/sourcedoc/backend.rst b/doc/source/sourcedoc/backend.rst index 4c6594cf6..abdc9df1d 100644 --- a/doc/source/sourcedoc/backend.rst +++ b/doc/source/sourcedoc/backend.rst @@ -106,3 +106,13 @@ Agent Backend Djbdns :private-members: :undoc-members: :show-inheritance: + +Agent Backend MSDNS +==================== + +.. automodule:: designate.backend.agent_backend.impl_msdns + :members: + :special-members: + :private-members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/support-matrix.ini b/doc/source/support-matrix.ini index da5c3fa98..92c7466b7 100644 --- a/doc/source/support-matrix.ini +++ b/doc/source/support-matrix.ini @@ -56,6 +56,8 @@ backend-impl-denominator=Denominator backend-impl-knot2-agent=Knot2 (Agent) backend-impl-djbdns-agent=Djbdns (Agent) backend-impl-gdnsd-agent=Gdnsd (Agent) +backend-impl-msdns-agent=Microsoft DNS (Agent) + [backends.backend-impl-bind9] @@ -95,6 +97,9 @@ maintainers=Infoblox OpenStack Team [backends.backend-impl-denominator] type=agent +[backends.backend-impl-msdns-agent] +type=agent + [grades] valid-grades=integrated,master-compatible,release-compatible,untested,failing,known-broken,experimental diff --git a/etc/designate/designate.conf.sample b/etc/designate/designate.conf.sample index 6870f8fc9..7ad60f155 100644 --- a/etc/designate/designate.conf.sample +++ b/etc/designate/designate.conf.sample @@ -492,6 +492,9 @@ debug = False #confdir_path = /etc/gdnsd #query_destination = 127.0.0.1 +[backend:agent:msdns] +#query_destination = 127.0.0.1 + ######################## ## Library Configuration ######################## diff --git a/requirements.txt b/requirements.txt index 722fdd243..96031f991 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,3 +47,4 @@ Werkzeug>=0.7 # BSD License python-memcached>=1.56 # PSF tooz>=1.28.0 # Apache-2.0 debtcollector>=1.2.0 # Apache-2.0 +os-win>=0.2.3 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index c1db2076d..cd7512f52 100644 --- a/setup.cfg +++ b/setup.cfg @@ -98,6 +98,7 @@ designate.backend.agent_backend = denominator = designate.backend.agent_backend.impl_denominator:DenominatorBackend fake = designate.backend.agent_backend.impl_fake:FakeBackend gdnsd = designate.backend.agent_backend.impl_gdnsd:GdnsdBackend + msdns = designate.backend.agent_backend.impl_msdns:MSDNSBackend designate.network_api = fake = designate.network_api.fake:FakeNetworkAPI