Browse Source

Add Knot DNS backend

Change-Id: I885c003afc2496ba4aa9631568be02e7d4654e52
Federico Ceratto 3 years ago
parent
commit
5d328f07aa

+ 3
- 3
designate/agent/__init__.py View File

@@ -47,12 +47,12 @@ OPTS = [
47 47
     cfg.ListOpt('masters', default=[],
48 48
                 help='List of masters for the Agent, format ip:port'),
49 49
     cfg.StrOpt('backend-driver', default='bind9',
50
-               help='The backend driver to use'),
50
+               help='The backend driver to use: bind9 or knot2'),
51 51
     cfg.StrOpt('transfer-source',
52 52
                help='An IP address to be used to fetch zones transferred in'),
53 53
     cfg.FloatOpt('notify-delay', default=0.0,
54
-               help='Delay after a NOTIFY arrives for a zone that the Agent '
55
-               'will pause and drop subsequent NOTIFYs for that zone'),
54
+                 help='Delay after a NOTIFY arrives for a zone that the Agent '
55
+                 'will pause and drop subsequent NOTIFYs for that zone'),
56 56
 ]
57 57
 
58 58
 cfg.CONF.register_opts(OPTS, group='service:agent')

+ 216
- 0
designate/backend/agent_backend/impl_knot2.py View File

@@ -0,0 +1,216 @@
1
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
2
+#
3
+# Author: Federico Ceratto <federico.ceratto@hpe.com>
4
+#
5
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+# not use this file except in compliance with the License. You may obtain
7
+# a copy of the License at
8
+#
9
+#      http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+# License for the specific language governing permissions and limitations
15
+# under the License.
16
+
17
+"""
18
+backend.agent_backend.impl_knot2
19
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20
+Knot DNS agent backend
21
+
22
+Create, update, delete zones locally on a Knot DNS resolver using the
23
+knotc utility.
24
+
25
+Supported Knot versions: >= 2.1, < 3
26
+
27
+`User documentation <backends/knot2_agent.html>`_
28
+
29
+.. WARNING::
30
+
31
+    Untested, do not use in production.
32
+
33
+.. NOTE::
34
+
35
+    If the backend is killed during a configuration transaction it might be
36
+    required to manually abort the transaction with `sudo knotc conf-abort`
37
+
38
+Configured in [service:agent:knot2]
39
+"""
40
+
41
+from oslo_concurrency import lockutils
42
+from oslo_concurrency.processutils import ProcessExecutionError
43
+from oslo_config import cfg
44
+from oslo_log import log as logging
45
+
46
+from designate import exceptions
47
+from designate.backend.agent_backend import base
48
+from designate.i18n import _LI
49
+from designate.i18n import _LE
50
+from designate.utils import execute
51
+
52
+LOG = logging.getLogger(__name__)
53
+CFG_GROUP = 'backend:agent:knot2'
54
+# rootwrap requires a command name instead of full path
55
+KNOTC_DEFAULT_PATH = 'knotc'
56
+
57
+# TODO(Federico) on zone creation and update, agent.handler unnecessarily
58
+# perfors AXFR from MiniDNS to the Agent to populate the `zone` argument
59
+# (needed by the Bind backend)
60
+
61
+
62
+class Knot2Backend(base.AgentBackend):
63
+    __plugin_name__ = 'knot2'
64
+    __backend_status__ = 'untested'
65
+    _lock_name = 'knot2.lock'
66
+
67
+    @classmethod
68
+    def get_cfg_opts(cls):
69
+        group = cfg.OptGroup(
70
+            name='backend:agent:knot2', title="Configuration for Knot2 backend"
71
+        )
72
+        opts = [
73
+            cfg.StrOpt('knotc-cmd-name',
74
+                       help='knotc executable path or rootwrap command name',
75
+                       default=KNOTC_DEFAULT_PATH),
76
+            cfg.StrOpt('query-destination', default='127.0.0.1',
77
+                       help='Host to query when finding zones')
78
+        ]
79
+        return [(group, opts)]
80
+
81
+    def __init__(self, *a, **kw):
82
+        """Configure the backend"""
83
+        super(Knot2Backend, self).__init__(*a, **kw)
84
+
85
+        self._knotc_cmd_name = cfg.CONF[CFG_GROUP].knotc_cmd_name
86
+
87
+    def start(self):
88
+        """Start the backend"""
89
+        LOG.info(_LI("Started knot2 backend"))
90
+
91
+    def _execute_knotc(self, *knotc_args, **kw):
92
+        """Run the Knot client and check the output
93
+
94
+        :param expected_output: expected output (default: 'OK')
95
+        :type expected_output: str
96
+        :param expected_error: expected alternative output, will be \
97
+        logged as info(). Default: not set.
98
+        :type expected_error: str
99
+        """
100
+        # Knotc returns "0" even on failure, we have to check for 'OK'
101
+        # https://gitlab.labs.nic.cz/labs/knot/issues/456
102
+
103
+        LOG.debug("Executing knotc with %r", knotc_args)
104
+        expected = kw.get('expected_output', 'OK')
105
+        expected_alt = kw.get('expected_error', None)
106
+        try:
107
+            out, err = execute(self._knotc_cmd_name, *knotc_args)
108
+            out = out.rstrip()
109
+            LOG.debug("Command output: %r" % out)
110
+            if out != expected:
111
+                if expected_alt is not None and out == expected_alt:
112
+                    LOG.info(_LI("Ignoring error: %r"), out)
113
+                else:
114
+                    raise ProcessExecutionError(stdout=out, stderr=err)
115
+
116
+        except ProcessExecutionError as e:
117
+            LOG.error(_LE("Command output: %(out)r Stderr: %(err)r"), {
118
+                'out': e.stdout, 'err': e.stderr
119
+            })
120
+            raise exceptions.Backend(e)
121
+
122
+    def _start_minidns_to_knot_axfr(self, zone_name):
123
+        """Instruct Knot to request an AXFR from MiniDNS. No need to lock
124
+        or enter a configuration transaction.
125
+        """
126
+        self._execute_knotc('zone-refresh', zone_name)
127
+
128
+    def _modify_zone(self, *knotc_args, **kw):
129
+        """Create or delete a zone while locking, and within a
130
+        Knot transaction.
131
+        Knot supports only one config transaction at a time.
132
+
133
+        :raises: exceptions.Backend
134
+        """
135
+        with lockutils.lock(self._lock_name):
136
+            self._execute_knotc('conf-begin')
137
+            try:
138
+                self._execute_knotc(*knotc_args, **kw)
139
+                # conf-diff can be used for debugging
140
+                # self._execute_knotc('conf-diff')
141
+            except Exception as e:
142
+                self._execute_knotc('conf-abort')
143
+                LOG.info(_LI("Zone change aborted: %r"), e)
144
+                raise e
145
+            else:
146
+                self._execute_knotc('conf-commit')
147
+
148
+    def find_zone_serial(self, zone_name):
149
+        """Get serial from a zone by running knotc
150
+
151
+        :returns: serial (int or None)
152
+        :raises: exceptions.Backend
153
+        """
154
+        zone_name = zone_name.rstrip('.')
155
+        LOG.debug("Finding %s", zone_name)
156
+        # Output example:
157
+        # [530336536.com.] type: slave | serial: 0 | next-event: idle |
158
+        # auto-dnssec: disabled]
159
+        try:
160
+            out, err = execute(self._knotc_cmd_name, 'zone-status', zone_name)
161
+        except ProcessExecutionError as e:
162
+            if 'no such zone' in e.stdout:
163
+                # Zone not found
164
+                return None
165
+
166
+            LOG.error(_LE("Command output: %(out)r Stderr: %(err)r"), {
167
+                'out': e.stdout, 'err': e.stderr
168
+            })
169
+            raise exceptions.Backend(e)
170
+
171
+        try:
172
+            serial = out.split('|')[1].split()[1]
173
+            return int(serial)
174
+        except Exception as e:
175
+            LOG.error(_LE("Unable to parse knotc output: %r"), out)
176
+            raise exceptions.Backend("Unexpected knotc zone-status output")
177
+
178
+    def create_zone(self, zone):
179
+        """Create a new Zone by executing knotc
180
+        Do not raise exceptions if the zone already exists.
181
+
182
+        :param zone: zone to be  created
183
+        :type zone: raw pythondns Zone
184
+        """
185
+        zone_name = zone.origin.to_text().rstrip('.')
186
+        LOG.debug("Creating %s", zone_name)
187
+        # The zone might be already in place due to a race condition between
188
+        # checking if the zone is there and creating it across different
189
+        # greenlets
190
+        self._modify_zone('conf-set', 'zone[%s]' % zone_name,
191
+                          expected_error='duplicate identifier')
192
+
193
+        LOG.debug("Triggering initial AXFR from MiniDNS to Knot for %s",
194
+                  zone_name)
195
+        self._start_minidns_to_knot_axfr(zone_name)
196
+
197
+    def update_zone(self, zone):
198
+        """Instruct Knot DNS to perform AXFR from MiniDNS
199
+
200
+        :param zone: zone to be  created
201
+        :type zone: raw pythondns Zone
202
+        """
203
+        zone_name = zone.origin.to_text()
204
+        LOG.debug("Triggering AXFR from MiniDNS to Knot for %s", zone_name)
205
+        self._start_minidns_to_knot_axfr(zone_name)
206
+
207
+    def delete_zone(self, zone_name):
208
+        """Delete a new Zone by executing knotc
209
+        Do not raise exceptions if the zone does not exist.
210
+
211
+        :param zone_name: zone name
212
+        :type zone_name: str
213
+        """
214
+        LOG.debug('Delete Zone: %s' % zone_name)
215
+        self._modify_zone('conf-unset', 'zone[%s]' % zone_name,
216
+                          expected_error='invalid identifier')

+ 3
- 1
designate/dnsutils.py View File

@@ -329,7 +329,9 @@ def dnspythonrecord_to_recordset(rname, rdataset):
329 329
 
330 330
 def do_axfr(zone_name, servers, timeout=None, source=None):
331 331
     """
332
-    Performs an AXFR for a given zone name
332
+    Requests an AXFR for a given zone name and process the response
333
+
334
+    :returns: Zone instance from dnspython
333 335
     """
334 336
     random.shuffle(servers)
335 337
     timeout = timeout or cfg.CONF["service:mdns"].xfr_timeout

+ 0
- 0
designate/tests/unit/test_agent/__init__.py View File


+ 0
- 0
designate/tests/unit/test_agent/test_backends/__init__.py View File


+ 200
- 0
designate/tests/unit/test_agent/test_backends/test_knot2.py View File

@@ -0,0 +1,200 @@
1
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
2
+#
3
+# Author: Federico Ceratto <federico.ceratto@hpe.com>
4
+#
5
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+# not use this file except in compliance with the License. You may obtain
7
+# a copy of the License at
8
+#
9
+#      http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+# License for the specific language governing permissions and limitations
15
+# under the License.
16
+
17
+"""
18
+    Unit-test the Knot 2 agent backend
19
+    knotc is not being executed
20
+"""
21
+
22
+from mock import call
23
+from oslo_concurrency.processutils import ProcessExecutionError
24
+import dns.zone
25
+import fixtures
26
+import mock
27
+
28
+from designate import exceptions
29
+from designate.backend.agent_backend.impl_knot2 import Knot2Backend
30
+from designate.tests import TestCase
31
+import designate.backend.agent_backend.impl_knot2  # noqa
32
+
33
+
34
+class Knot2AgentBackendBasicUnitTestCase(TestCase):
35
+
36
+    def test_init(self):
37
+        kb = Knot2Backend('foo')
38
+        self.assertEqual('knotc', kb._knotc_cmd_name)
39
+
40
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
41
+    def test__execute_knotc_ok(self, mock_exe):
42
+        mock_exe.return_value = ('OK', '')
43
+        kb = Knot2Backend('foo')
44
+        kb._execute_knotc('a1', 'a2')
45
+        mock_exe.assert_called_with('knotc', 'a1', 'a2')
46
+        self.assertEqual(1, mock_exe.call_count)
47
+
48
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
49
+    def test__execute_knotc_expected_error(self, mock_exe):
50
+        mock_exe.return_value = ('xyz', '')
51
+        kb = Knot2Backend('foo')
52
+        kb._execute_knotc('a1', 'a2', expected_error='xyz')
53
+        mock_exe.assert_called_once_with('knotc', 'a1', 'a2')
54
+
55
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
56
+    def test__execute_knotc_expected_output(self, mock_exe):
57
+        mock_exe.return_value = ('xyz', '')
58
+        kb = Knot2Backend('foo')
59
+        kb._execute_knotc('a1', 'a2', expected_output='xyz')
60
+        mock_exe.assert_called_once_with('knotc', 'a1', 'a2')
61
+
62
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
63
+    def test__execute_knotc_with_error(self, mock_exe):
64
+        mock_exe.return_value = ('xyz', '')
65
+        kb = Knot2Backend('foo')
66
+        self.assertRaises(
67
+            exceptions.Backend,
68
+            kb._execute_knotc, 'a1', 'a2')
69
+        mock_exe.assert_called_once_with('knotc', 'a1', 'a2')
70
+
71
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
72
+    def test__execute_knotc_raising_exception(self, mock_exe):
73
+        mock_exe.side_effect = ProcessExecutionError
74
+        kb = Knot2Backend('foo')
75
+        self.assertRaises(
76
+            exceptions.Backend,
77
+            kb._execute_knotc, 'a1', 'a2')
78
+        mock_exe.assert_called_once_with('knotc', 'a1', 'a2')
79
+
80
+
81
+class Knot2AgentBackendUnitTestCase(TestCase):
82
+
83
+    def _create_dnspy_zone(self, name):
84
+        zone_text = (
85
+            '$ORIGIN %(name)s\n%(name)s 3600 IN SOA %(ns)s '
86
+            'email.email.com. 1421777854 3600 600 86400 3600\n%(name)s '
87
+            '3600 IN NS %(ns)s\n') % {'name': name, 'ns': 'ns1.designate.com'}
88
+
89
+        return dns.zone.from_text(zone_text, check_origin=False)
90
+
91
+    def setUp(self):
92
+        super(Knot2AgentBackendUnitTestCase, self).setUp()
93
+        self.kb = Knot2Backend('foo')
94
+        self.patch_ob(self.kb, '_execute_knotc')
95
+
96
+    def tearDown(self):
97
+        super(Knot2AgentBackendUnitTestCase, self).tearDown()
98
+
99
+    def patch_ob(self, *a, **kw):
100
+        self.useFixture(fixtures.MockPatchObject(*a, **kw))
101
+
102
+    def test_create_zone(self, *mocks):
103
+        zone = self._create_dnspy_zone('example.org')
104
+        self.kb.create_zone(zone)
105
+        self.kb._execute_knotc.assert_has_calls([
106
+            call('conf-begin'),
107
+            call('conf-set', 'zone[example.org]',
108
+                 expected_error='duplicate identifier'),
109
+            call('conf-commit'),
110
+            call('zone-refresh', 'example.org')
111
+        ])
112
+
113
+    def test_create_zone_already_there(self, *mocks):
114
+        self.kb._execute_knotc.return_value = 'duplicate identifier'
115
+        zone = self._create_dnspy_zone('example.org')
116
+        self.kb.create_zone(zone)
117
+        self.kb._execute_knotc.assert_has_calls([
118
+            call('conf-begin'),
119
+            call('conf-set', 'zone[example.org]',
120
+                 expected_error='duplicate identifier'),
121
+            call('conf-commit'),
122
+            call('zone-refresh', 'example.org')
123
+        ])
124
+
125
+    def test__start_minidns_to_knot_axfr(self):
126
+        self.kb._start_minidns_to_knot_axfr('foo')
127
+        self.kb._execute_knotc.assert_called_with('zone-refresh', 'foo')
128
+
129
+    @mock.patch('oslo_concurrency.lockutils.lock')
130
+    def test__modify_zone(self, *mocks):
131
+        self.kb._modify_zone('blah', 'bar')
132
+        self.assertEqual(3, self.kb._execute_knotc.call_count)
133
+        self.kb._execute_knotc.assert_called_with('conf-commit')
134
+
135
+    @mock.patch('oslo_concurrency.lockutils.lock')
136
+    def test__modify_zone_exception(self, *mocks):
137
+        # Raise an exception during the second call to _execute_knotc
138
+        self.kb._execute_knotc.side_effect = [None, exceptions.Backend, None]
139
+        self.assertRaises(
140
+            exceptions.Backend,
141
+            self.kb._modify_zone, 'blah', 'bar')
142
+        self.assertEqual(3, self.kb._execute_knotc.call_count)
143
+        self.kb._execute_knotc.assert_has_calls([
144
+            call('conf-begin'),
145
+            call('blah', 'bar'),
146
+            call('conf-abort'),
147
+        ])
148
+
149
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
150
+    def test_find_zone_serial(self, mock_exe):
151
+        mock_exe.return_value = "[example.com.] type: slave | serial: 20 | " \
152
+            "next-event: idle | auto-dnssec: disabled]", ""
153
+        serial = self.kb.find_zone_serial('example.com')
154
+        self.assertEqual(20, serial)
155
+
156
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
157
+    def test_find_zone_serial__zone_not_found(self, mock_exe):
158
+        mock_exe.side_effect = ProcessExecutionError(
159
+            "error: [example.com.] (no such zone found)")
160
+        serial = self.kb.find_zone_serial('example.com')
161
+        self.assertEqual(None, serial)
162
+
163
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
164
+    def test_find_zone_serial_unexpected_output(self, mock_exe):
165
+        mock_exe.return_value = "bogus output", ""
166
+        self.assertRaises(
167
+            exceptions.Backend,
168
+            self.kb.find_zone_serial, 'example.com')
169
+
170
+    @mock.patch('designate.backend.agent_backend.impl_knot2.execute')
171
+    def test_find_zone_serial_error(self, mock_exe):
172
+        mock_exe.side_effect = ProcessExecutionError("blah")
173
+        self.assertRaises(
174
+            exceptions.Backend,
175
+            self.kb.find_zone_serial, 'example.com')
176
+
177
+    def test_update_zone(self):
178
+        zone = self._create_dnspy_zone('example.org')
179
+        self.kb.update_zone(zone)
180
+        self.kb._execute_knotc.assert_called_once_with(
181
+            'zone-refresh', 'example.org')
182
+
183
+    def test_delete_zone(self):
184
+        self.kb.delete_zone('example.org')
185
+        self.kb._execute_knotc.assert_has_calls([
186
+            call('conf-begin'),
187
+            call('conf-unset', 'zone[example.org]',
188
+                 expected_error='invalid identifier'),
189
+            call('conf-commit'),
190
+        ])
191
+
192
+    def test_delete_zone_already_gone(self):
193
+        self.kb._execute_knotc.return_value = 'duplicate identifier'
194
+        self.kb.delete_zone('example.org')
195
+        self.kb._execute_knotc.assert_has_calls([
196
+            call('conf-begin'),
197
+            call('conf-unset', 'zone[example.org]',
198
+                 expected_error='invalid identifier'),
199
+            call('conf-commit'),
200
+        ])

+ 130
- 0
devstack/designate_plugins/backend-agent-knot2 View File

@@ -0,0 +1,130 @@
1
+# Configure the Knot2 agent backend for Devstack
2
+
3
+# Enable this pluging by adding these line to local.conf:
4
+#
5
+# DESIGNATE_BACKEND_DRIVER=agent
6
+# DESIGNATE_AGENT_BACKEND_DRIVER=knot2
7
+
8
+# install_designate_agent_backend - install any external requirements
9
+# configure_designate_agent_backend - make configuration changes, including those to other services
10
+# init_designate_agent_backend - initialize databases, etc.
11
+# start_designate_agent_backend - start any external services
12
+# stop_designate_agent_backend - stop any external services
13
+# cleanup_designate_agent_backend - remove transient data and cache
14
+
15
+# Save trace setting
16
+DP_AGENT_KNOT_XTRACE=$(set +o | grep xtrace)
17
+set +o xtrace
18
+
19
+# Defaults
20
+# --------
21
+KNOT_SERVICE_NAME=knot
22
+KNOT_CFG_DIR=/etc/knot
23
+KNOT_VAR_DIR=/var/lib/knot
24
+KNOT_USER=knot
25
+KNOT_GROUP=knot
26
+
27
+if is_fedora; then
28
+    echo "only Ubuntu is supported right now"
29
+fi
30
+
31
+# Entry Points
32
+# ------------
33
+
34
+# install_designate_agent_backend - install any external requirements
35
+function install_designate_agent_backend {
36
+    if is_ubuntu; then
37
+        # https://github.com/oerdnj/deb.sury.org/issues/56
38
+        LC_ALL=C.UTF-8 sudo add-apt-repository --yes ppa:cz.nic-labs/knot-dns
39
+        sudo apt-get update
40
+        echo "---- available knot package ---"
41
+        sudo apt-cache show knot
42
+        echo "---- installing knot ---"
43
+        sudo apt-get install -y knot
44
+    else
45
+        echo "only Ubuntu is supported right now"
46
+        exit 1
47
+    fi
48
+}
49
+
50
+# configure_designate_agent_backend - make configuration changes, including those to other services
51
+function configure_designate_agent_backend {
52
+
53
+    # [re]create the config database
54
+    stop_service knot
55
+    sudo sh -c "rm /var/lib/knot/*zone /var/lib/knot/*/*.mdb -f"
56
+    sudo knotc conf-init -v
57
+
58
+    # Create /etc/default/knot
59
+    cat <<EOF | sudo tee /etc/default/knot
60
+# Created by $0 on $(date)
61
+KNOTD_ARGS="-C /var/lib/knot/confdb"
62
+EOF
63
+
64
+    # Apply this workaround for bug
65
+    # https://gitlab.labs.nic.cz/labs/knot/issues/455
66
+    sudo sh -c "cd /etc/default/ && test -f knotd || ln -s knot knotd"
67
+
68
+    start_service knot
69
+    sleep 1
70
+
71
+    # Ensure the confdb is present
72
+    sudo test -f /var/lib/knot/confdb/data.mdb
73
+
74
+    # Create the configuration
75
+    MINIDNS_IPADDR=$DESIGNATE_SERVICE_HOST
76
+
77
+    sudo knotc conf-begin
78
+    sudo knotc conf-set server.listen $DESIGNATE_SERVICE_HOST@$DESIGNATE_SERVICE_PORT_DNS
79
+    sudo knotc conf-set remote[minidns]
80
+    sudo knotc conf-set remote[minidns].address $DESIGNATE_SERVICE_HOST@$DESIGNATE_SERVICE_PORT_MDNS
81
+    sudo knotc conf-set template[default]
82
+    sudo knotc conf-set template[default].master minidns
83
+    sudo knotc conf-set template[default].acl acl_minidns
84
+    sudo knotc conf-set template[default].semantic-checks on
85
+    # Create localdomain as a workaround for
86
+    # https://gitlab.labs.nic.cz/labs/knot/issues/457
87
+    sudo knotc conf-set zone[localdomain]
88
+    sudo knotc conf-set log.any info
89
+    sudo knotc conf-set log.target syslog
90
+    sudo knotc conf-set acl[acl_minidns]
91
+    sudo knotc conf-set acl[acl_minidns].address $DESIGNATE_SERVICE_HOST
92
+    sudo knotc conf-set acl[acl_minidns].action notify
93
+    echo "--------------"
94
+    sudo knotc conf-diff
95
+    echo "--------------"
96
+    sudo knotc conf-commit
97
+    sudo knotc conf-check
98
+
99
+    # Ensure the zone survives a restart
100
+    sleep 1
101
+    sudo service knot restart
102
+    sleep 1
103
+    sudo knotc zone-status localdomain
104
+
105
+    echo "Testing Knot: this should return the daemon version"
106
+    dig @$DESIGNATE_SERVICE_HOST -p$DESIGNATE_SERVICE_PORT_DNS version.server CH TXT
107
+}
108
+
109
+# init_designate_agent_backend - initialize databases, etc.
110
+function init_designate_agent_backend {
111
+    :
112
+}
113
+
114
+# start_designate_agent_backend - start any external services
115
+function start_designate_agent_backend {
116
+    start_service knot
117
+}
118
+
119
+# stop_designate_agent_backend - stop any external services
120
+function stop_designate_agent_backend {
121
+    stop_service knot
122
+}
123
+
124
+# cleanup_designate_agent_backend - remove transient data and cache
125
+function cleanup_designate_agent_backend {
126
+    :
127
+}
128
+
129
+# Restore xtrace
130
+$DP_AGENT_KNOT_XTRACE

+ 11
- 0
doc/source/backend.rst View File

@@ -76,3 +76,14 @@ Backend Powerdns
76 76
     :members:
77 77
     :undoc-members:
78 78
     :show-inheritance:
79
+
80
+Agent Backend KnotDNS
81
+=====================
82
+
83
+.. automodule:: designate.backend.agent_backend.impl_knot2
84
+    :members:
85
+    :special-members:
86
+    :private-members:
87
+    :undoc-members:
88
+    :show-inheritance:
89
+

+ 201
- 0
doc/source/backends/knot2_agent.rst View File

@@ -0,0 +1,201 @@
1
+..
2
+    Copyright 2016 Hewlett Packard Enterprise Development Company LP
3
+
4
+    Author: Federico Ceratto <federico.ceratto@hpe.com>
5
+
6
+    Licensed under the Apache License, Version 2.0 (the "License"); you may
7
+    not use this file except in compliance with the License. You may obtain
8
+    a copy of the License at
9
+
10
+        http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+    Unless required by applicable law or agreed to in writing, software
13
+    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+    License for the specific language governing permissions and limitations
16
+    under the License.
17
+
18
+Knot DNS 2 Agent backend
19
+************************
20
+
21
+
22
+User documentation
23
+==================
24
+
25
+This page documents the Agent backend for `Knot DNS <https://www.knot-dns.cz/>`_.
26
+
27
+The agent runs on the same host as the resolver. It receives DNS messages from Mini DNS using private DNS OPCODEs and classes and creates or deletes zones on Knot using the knotc tool.
28
+It also instructs Knot to request AXFR from MiniDNS when a zone is created or updated.
29
+
30
+Support matrix:
31
+
32
+* 2.0 and older: not supported
33
+* 2.1.1: supported, tested
34
+* 2.2.0: `affected by a bug <https://gitlab.labs.nic.cz/labs/knot/issues/460>`_
35
+
36
+
37
+`Knot DNS 2.x documentation <https://www.knot-dns.cz/docs/2.x/singlehtml/>`_
38
+
39
+Setting up Knot DNS on Ubuntu Trusty
40
+------------------------------------
41
+
42
+Knot DNS 2.1 is not part of Ubuntu Trusty. You can ues the CZ.NIC PPA with:
43
+
44
+.. code-block:: bash
45
+
46
+    service pdns stop
47
+    add-apt-repository ppa:cz.nic-labs/knot-dns
48
+    apt-get update
49
+    apt-get install knot
50
+
51
+
52
+Configuring Knot DNS
53
+--------------------
54
+
55
+Assuming Knot has been freshly installed on the system, run as root:
56
+
57
+.. code-block:: bash
58
+
59
+    # Monitor syslog during the next steps
60
+    tail -f /var/log/syslog
61
+
62
+    # Start the daemon, ensure it's running
63
+    service knot start
64
+    netstat -npltu | grep knotd
65
+
66
+    # Create the config database
67
+    knotc conf-init
68
+
69
+    # Edit /etc/default/knot
70
+    # Set the variable:
71
+    # KNOTD_ARGS="-C /var/lib/knot/confdb"
72
+
73
+    # Restart
74
+    service knot restart
75
+
76
+    # Check if the deamon is still running from the conf file in /etc/knot/
77
+    ps axuw | grep knotd
78
+
79
+    # if so, apply this workaround for bug
80
+    # https://gitlab.labs.nic.cz/labs/knot/issues/455
81
+    ( cd /etc/default/ && ln -s knot knotd )
82
+    service knot restart
83
+    ps axuw | grep knotd
84
+
85
+    # Ensure the confdb is present
86
+    test -f /var/lib/knot/confdb/data.mdb && echo OK
87
+
88
+    # Create the configuration
89
+    # Populate the variable with the MiniDNS ipaddr:
90
+    MINIDNS_IPADDR=
91
+
92
+    knotc conf-begin
93
+    knotc conf-set server.listen 0.0.0.0@53
94
+    # To listen on IPv6 as well, also run this:
95
+    # knotc conf-set server.listen '::@53'
96
+    knotc conf-set remote[minidns]
97
+    knotc conf-set remote[minidns].address $MINIDNS_IPADDR@5354
98
+    knotc conf-set template[default]
99
+    knotc conf-set template[default].master minidns
100
+    knotc conf-set template[default].acl acl_minidns
101
+    knotc conf-set template[default].semantic-checks on
102
+    knotc conf-set zone[example.com]
103
+    knotc conf-set log.any info
104
+    knotc conf-set log.target syslog
105
+    knotc conf-set acl[acl_minidns]
106
+    knotc conf-set acl[acl_minidns].address $MINIDNS_IPADDR
107
+    knotc conf-set acl[acl_minidns].action notify
108
+    # Review the changes and commit
109
+    knotc conf-diff
110
+    knotc conf-commit
111
+
112
+    # Optionally check and back up the conf
113
+    knotc conf-check
114
+    knotc conf-export knot.conf.bak && cat knot.conf.bak
115
+
116
+    # Ensure the zone survives a restart
117
+    service knot restart
118
+    knotc zone-status example.com
119
+
120
+    # Test Knot: this should return the version
121
+    dig @127.0.0.1 version.server CH TXT
122
+
123
+If needed, create a rootwrap filter, as root:
124
+
125
+.. code-block:: bash
126
+
127
+    cat > /etc/designate/rootwrap.d/knot2.filters <<EOF
128
+    # cmd-name: filter-name, raw-command, user, args
129
+    [Filters]
130
+    knotc: CommandFilter, /usr/sbin/knotc, root
131
+    EOF
132
+
133
+    # Check the filter:
134
+    sudo /usr/local/bin/designate-rootwrap /etc/designate/rootwrap.conf knotc status
135
+
136
+Configure the "service.agent" and "backend.agent.knot2" sections in /etc/designate/designate.conf
137
+
138
+Look in designate.conf.example for examples
139
+
140
+Create an agent pool:
141
+
142
+.. code-block:: bash
143
+
144
+    # Fetch the existing pool(s) if needed or start from scratch
145
+    designate-manage pool generate_file --file /tmp/pool.yaml
146
+    # Edit the file (see below) and reload it as:
147
+    designate-manage pool update --file /tmp/pool.yaml
148
+
149
+The "targets" section in pool.yaml should look like:
150
+
151
+.. code-block:: ini
152
+
153
+  targets:
154
+  - description: knot2 agent
155
+    masters:
156
+    - host: <MiniDNS IP addr>
157
+      port: 5354
158
+    options: {}
159
+    options:
160
+    - host: <Agent IP addr>
161
+      port: 5358
162
+    type: agent
163
+
164
+Developer documentation
165
+=======================
166
+
167
+Devstack testbed
168
+----------------
169
+
170
+Follow "Setting up Knot DNS on Ubuntu Trusty"
171
+
172
+Configure Knot to slave from MiniDNS on 192.168.121.131
173
+
174
+Knotd configuration example (sudo knotc conf-export <filename>):
175
+
176
+.. code-block:: yaml
177
+
178
+    # Configuration export (Knot DNS 2.1.1)
179
+
180
+    server:
181
+        listen: "0.0.0.0@53"
182
+
183
+    log:
184
+    - target: "syslog"
185
+        any: "debug"
186
+
187
+    acl:
188
+    - id: "acl_minidns"
189
+        address: [ "192.168.121.131" ]
190
+        action: [ "notify" ]
191
+
192
+    remote:
193
+    - id: "minidns"
194
+        address: "192.168.121.131@5354"
195
+
196
+    template:
197
+    - id: "default"
198
+        master: "minidns"
199
+        acl: "acl_minidns"
200
+        semantic-checks: "on"
201
+

+ 5
- 1
doc/source/support-matrix.ini View File

@@ -47,13 +47,14 @@ backend-impl-bind9=Bind9
47 47
 backend-impl-powerdns-mysql=Power DNS (MySQL)
48 48
 backend-impl-designate=Designate to Designate
49 49
 backend-impl-dynect=DynECT
50
-backend-impl-dynect=DynECT
51 50
 backend-impl-akamai=Akamai eDNS
52 51
 backend-impl-infoblox-xfr=Infoblox (XFR)
53 52
 backend-impl-nsd4=NSD4
54 53
 backend-impl-agent=Agent
55 54
 backend-impl-bind9-agent=Bind9 (Agent)
56 55
 backend-impl-denominator=Denominator
56
+backend-impl-knot2-agent=Knot2 (Agent)
57
+
57 58
 
58 59
 [backends.backend-impl-bind9]
59 60
 
@@ -75,6 +76,9 @@ maintainers=HP DNSaaS Team <dnsaas@hp.com>
75 76
 [backends.backend-impl-bind9-agent]
76 77
 type=agent
77 78
 
79
+[backends.backend-impl-knot2-agent]
80
+type=agent
81
+
78 82
 [backends.backend-impl-infoblox-xfr]
79 83
 status=release-compatible
80 84
 maintainers=Infoblox OpenStack Team <openstack-maintainer@infoblox.com>

+ 17
- 3
etc/designate/designate.conf.sample View File

@@ -256,13 +256,19 @@ debug = False
256 256
 #-----------------------
257 257
 # Agent Service
258 258
 #-----------------------
259
+# The agent runs on the resolver hosts
259 260
 [service:agent]
260 261
 #workers = None
261 262
 #listen = 0.0.0.0:5358
262 263
 #tcp_backlog = 100
263 264
 #allow_notify = 127.0.0.1
265
+
266
+# MiniDNS IP address and port
264 267
 #masters = 127.0.0.1:5354
268
+
269
+# Set to "fake", "bind9" or "knot2"
265 270
 #backend_driver = fake
271
+
266 272
 #transfer_source = None
267 273
 #notify_delay = 0
268 274
 
@@ -441,9 +447,12 @@ debug = False
441 447
 #format = '%(hostname)s.%(project)s.%(domain)s'
442 448
 #format = '%(hostname)s.%(domain)s'
443 449
 
444
-#############################
450
+##############################
445 451
 ## Agent Backend Configuration
446
-#############################
452
+##############################
453
+
454
+# Set backend_driver in the [service:agent] section
455
+
447 456
 [backend:agent:bind9]
448 457
 #rndc_host = 127.0.0.1
449 458
 #rndc_port = 953
@@ -451,7 +460,12 @@ debug = False
451 460
 #rndc_key_file = /etc/rndc.key
452 461
 #zone_file_path = $state_path/zones
453 462
 #query_destination = 127.0.0.1
454
-#
463
+
464
+[backend:agent:knot2]
465
+# knotc command name when rootwrap is used. Location of the knotc executable
466
+# on the resolver host if rootwrap is not used
467
+#knotc_cmd_name = /usr/sbin/knotc
468
+
455 469
 [backend:agent:denominator]
456 470
 #name = dynect
457 471
 #config_file = /etc/denominator.conf

+ 3
- 0
etc/designate/rootwrap.d/knot2.filters View File

@@ -0,0 +1,3 @@
1
+# cmd-name: filter-name, raw-command, user, args
2
+[Filters]
3
+knotc: CommandFilter, /usr/sbin/knotc, root

+ 3
- 0
releasenotes/notes/knot-agent-backend-db2893aa97d85a1d.yaml View File

@@ -0,0 +1,3 @@
1
+---
2
+features:
3
+  - An experimental agent backend to support Knot DNS 2

+ 1
- 0
setup.cfg View File

@@ -91,6 +91,7 @@ designate.backend =
91 91
 
92 92
 designate.backend.agent_backend =
93 93
     bind9 = designate.backend.agent_backend.impl_bind9:Bind9Backend
94
+    knot2 = designate.backend.agent_backend.impl_knot2:Knot2Backend
94 95
     denominator = designate.backend.agent_backend.impl_denominator:DenominatorBackend
95 96
     fake = designate.backend.agent_backend.impl_fake:FakeBackend
96 97
 

Loading…
Cancel
Save