diff options
-rw-r--r-- | designate/backend/agent_backend/impl_msdns.py | 113 | ||||
-rw-r--r-- | designate/tests/unit/test_agent/test_backends/test_msdns.py | 122 | ||||
-rw-r--r-- | devstack/designate_plugins/backend-agent-msdns | 116 | ||||
-rw-r--r-- | doc/source/backends/msdns_agent.rst | 134 | ||||
-rw-r--r-- | doc/source/sourcedoc/backend.rst | 10 | ||||
-rw-r--r-- | doc/source/support-matrix.ini | 5 | ||||
-rw-r--r-- | etc/designate/designate.conf.sample | 3 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rw-r--r-- | setup.cfg | 1 |
9 files changed, 505 insertions, 0 deletions
diff --git a/designate/backend/agent_backend/impl_msdns.py b/designate/backend/agent_backend/impl_msdns.py new file mode 100644 index 00000000..949342fa --- /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 <abalutoiu@cloudbasesolutions.com> +# +# 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 00000000..ed798e5e --- /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 <abalutoiu@cloudbasesolutions.com> +# +# 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 00000000..1d459cef --- /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 <<EOF +--- +- name: default + description: DevStack MSDNS Pool + attributes: {} + + ns_records: + - hostname: $DESIGNATE_DEFAULT_NS_RECORD + priority: 1 + + nameservers: + - host: $DESIGNATE_MSDNS_HOST_IP + port: $DESIGNATE_MSDNS_HOST_PORT + + targets: + - type: agent + description: MSDNS Agent Instance + + masters: + - host: $DESIGNATE_SERVICE_HOST + port: $DESIGNATE_SERVICE_PORT_MDNS + + options: + host: $DESIGNATE_MSDNS_HOST_IP + port: $DESIGNATE_MSDNS_HOST_PORT +EOF + + echo "# Sample Config for Windows Agent service" | sudo tee ${DESIGNATE_CONF}.win + echo "# This file should be copied to the Windows DNS server and used as the Designate config file" | sudo tee -a ${DESIGNATE_CONF}.win + # Configure Agent Settings + iniset ${DESIGNATE_CONF}.win service:agent backend_driver $DESIGNATE_AGENT_BACKEND_DRIVER + iniset ${DESIGNATE_CONF}.win service:agent host $DESIGNATE_MSDNS_HOST_IP + iniset ${DESIGNATE_CONF}.win service:agent port $DESIGNATE_MSDNS_HOST_PORT + iniset ${DESIGNATE_CONF}.win service:agent masters "$DESIGNATE_MSDNS_MASTERS" +} + +# init_designate_agent_backend - initialize databases, etc. +function init_designate_agent_backend { + : +} + +# start_designate_agent_backend - start any external services +function start_designate_agent_backend { + : +} + +# stop_designate_agent_backend - stop any external services +function stop_designate_agent_backend { + : +} + +# cleanup_designate_agent_backend - remove transient data and cache +function cleanup_designate_agent_backend { + : +} + +# Restore xtrace +$DP_AGENT_MSDNS_XTRACE diff --git a/doc/source/backends/msdns_agent.rst b/doc/source/backends/msdns_agent.rst new file mode 100644 index 00000000..3f2976e6 --- /dev/null +++ b/doc/source/backends/msdns_agent.rst @@ -0,0 +1,134 @@ +.. + Copyright 2016 Cloudbase Solutions Srl + + Author: Alin Balutoiu <abalutoiu@cloudbasesolutions.com> + + 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 +<https://msdn.microsoft.com/en-us/library/windows/desktop/ms682757.aspx>`_ + +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 +<https://technet.microsoft.com/en-us/library/cc725925.aspx>`_ + +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 = <MiniDNS IP addr>: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: <MiniDNS IP addr> + port: 53 + options: {} + options: + - host: <Agent IP addr> + port: 5358 + type: agent diff --git a/doc/source/sourcedoc/backend.rst b/doc/source/sourcedoc/backend.rst index 4c6594cf..abdc9df1 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 da5c3fa9..92c7466b 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 <openstack-maintainer@infoblox.com> [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 6870f8fc..7ad60f15 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 722fdd24..96031f99 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 @@ -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 |