├── src ├── files │ └── .gitkeep ├── lib │ └── charm │ │ ├── __init__.py │ │ └── openstack │ │ ├── __init__.py │ │ └── designate.py ├── templates │ ├── openstack │ ├── rndc.key │ ├── mitaka │ │ ├── nova_sink.cfg │ │ ├── neutron_sink.cfg │ │ ├── pools.yaml │ │ └── designate.conf │ ├── newton │ │ ├── nova_sink.cfg │ │ └── neutron_sink.cfg │ ├── novarc │ ├── rocky │ │ ├── pools.yaml │ │ └── designate.conf │ ├── caracal │ │ └── designate.conf │ └── queens │ │ └── designate.conf ├── wheelhouse.txt ├── layer.yaml ├── test-requirements.txt ├── tests │ ├── tests.yaml │ └── bundles │ │ └── noble-caracal.yaml ├── metadata.yaml ├── tox.ini ├── README.md ├── icon.svg ├── reactive │ ├── designate_utils.py │ └── designate_handlers.py └── config.yaml ├── metadata.yaml ├── .stestr.conf ├── .gitreview ├── bindep.txt ├── .gitignore ├── rebuild ├── .zuul.yaml ├── osci.yaml ├── rename.sh ├── README.md ├── requirements.txt ├── charmcraft.yaml ├── test-requirements.txt ├── unit_tests ├── __init__.py ├── test_designate_utils.py ├── test_designate_handlers.py └── test_lib_charm_openstack_designate.py ├── tox.ini └── LICENSE /src/files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/charm/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /metadata.yaml: -------------------------------------------------------------------------------- 1 | src/metadata.yaml -------------------------------------------------------------------------------- /src/lib/charm/openstack/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=./unit_tests 3 | top_dir=./ 4 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/charm-designate.git 5 | -------------------------------------------------------------------------------- /bindep.txt: -------------------------------------------------------------------------------- 1 | libffi-dev [platform:dpkg] 2 | libpq-dev [platform:dpkg] 3 | libxml2-dev [platform:dpkg] 4 | libxslt1-dev [platform:dpkg] 5 | -------------------------------------------------------------------------------- /src/templates/openstack: -------------------------------------------------------------------------------- 1 | DAEMON_ARGS="--config-file=/etc/designate/designate.conf {{ options.nova_conf_args }} {{ options.neutron_conf_args }}" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | layers 3 | .tox 4 | interfaces 5 | builds 6 | deps 7 | .testrepository 8 | .stestr 9 | __pycache__ 10 | *.pyc 11 | *.charm 12 | -------------------------------------------------------------------------------- /src/wheelhouse.txt: -------------------------------------------------------------------------------- 1 | 2 | git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack 3 | 4 | git+https://github.com/juju/charm-helpers.git#egg=charmhelpers 5 | -------------------------------------------------------------------------------- /src/templates/rndc.key: -------------------------------------------------------------------------------- 1 | {% if dns_backend %} 2 | key "rndc-key" { 3 | algorithm {{ dns_backend.rndc_info.algorithm }}; 4 | secret "{{ dns_backend.rndc_info.secret }}"; 5 | }; 6 | {% endif %} 7 | -------------------------------------------------------------------------------- /rebuild: -------------------------------------------------------------------------------- 1 | # This file is used to trigger rebuilds 2 | # when dependencies of the charm change, 3 | # but nothing in the charm needs to. 4 | # simply change the uuid to something new 5 | d4e3aada-143c-47a3-be7e-925634ae6033 6 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | templates: 3 | - openstack-python3-charm-jobs 4 | - openstack-cover-jobs 5 | check: 6 | jobs: 7 | - charmbuild 8 | vars: 9 | charm_build_name: designate 10 | -------------------------------------------------------------------------------- /osci.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | templates: 3 | - charm-unit-jobs-py310 4 | - charm-functional-jobs 5 | vars: 6 | needs_charm_build: true 7 | charm_build_name: designate 8 | build_type: charmcraft 9 | charmcraft_channel: 3.x/stable 10 | -------------------------------------------------------------------------------- /src/templates/mitaka/nova_sink.cfg: -------------------------------------------------------------------------------- 1 | {% if options.nova_domain_id %} 2 | [handler:nova_fixed] 3 | zone_id = {{ options.nova_domain_id }} 4 | notification_topics = 'notifications_designate' 5 | control_exchange = 'nova' 6 | format = '{{ options.nova_record_format }}' 7 | {% endif %} 8 | -------------------------------------------------------------------------------- /src/templates/mitaka/neutron_sink.cfg: -------------------------------------------------------------------------------- 1 | {% if options.neutron_domain_id %} 2 | [handler:neutron_floatingip] 3 | zone_id = {{ options.neutron_domain_id }} 4 | notification_topics = notifications_designate 5 | control_exchange = 'neutron' 6 | format = '{{ options.neutron_record_format }}' 7 | {% endif %} 8 | -------------------------------------------------------------------------------- /src/templates/newton/nova_sink.cfg: -------------------------------------------------------------------------------- 1 | {% if options.nova_domain_id %} 2 | [handler:nova_fixed] 3 | zone_id = {{ options.nova_domain_id }} 4 | notification_topics = 'notifications_designate' 5 | control_exchange = 'nova' 6 | formatv4 = '{{ options.nova_record_format }}' 7 | formatv6 = '{{ options.nova_record_formatv6 }}' 8 | {% endif %} 9 | -------------------------------------------------------------------------------- /src/templates/newton/neutron_sink.cfg: -------------------------------------------------------------------------------- 1 | {% if options.neutron_domain_id %} 2 | [handler:neutron_floatingip] 3 | zone_id = {{ options.neutron_domain_id }} 4 | notification_topics = notifications_designate 5 | control_exchange = 'neutron' 6 | formatv4 = '{{ options.neutron_record_format }}' 7 | formatv6 = '{{ options.neutron_record_formatv6 }}' 8 | {% endif %} 9 | -------------------------------------------------------------------------------- /src/layer.yaml: -------------------------------------------------------------------------------- 1 | includes: ['layer:openstack-api', 'layer:leadership', 'interface:bind-rndc', 'interface:hacluster', 'interface:openstack-ha', 'interface:memcache', 'interface:designate', 'interface:nrpe-external-master'] 2 | options: 3 | basic: 4 | use_venv: True 5 | include_system_packages: False 6 | repo: https://github.com/openstack/charm-designate 7 | -------------------------------------------------------------------------------- /rename.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | charm=$(grep "charm_build_name" osci.yaml | awk '{print $2}') 3 | echo "renaming ${charm}_*.charm to ${charm}.charm" 4 | echo -n "pwd: " 5 | pwd 6 | ls -al 7 | echo "Removing bad downloaded charm maybe?" 8 | if [[ -e "${charm}.charm" ]]; 9 | then 10 | rm "${charm}.charm" 11 | fi 12 | echo "Renaming charm here." 13 | mv ${charm}_*.charm ${charm}.charm 14 | -------------------------------------------------------------------------------- /src/test-requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is managed centrally by release-tools and should not be modified 2 | # within individual charm repos. See the 'global' dir contents for available 3 | # choices of *requirements.txt files for OpenStack Charms: 4 | # https://github.com/openstack-charmers/release-tools 5 | # 6 | 7 | # Functional Test Requirements (let Zaza's dependencies solve all dependencies here!) 8 | git+https://github.com/openstack-charmers/zaza.git#egg=zaza 9 | git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack 10 | -------------------------------------------------------------------------------- /src/tests/tests.yaml: -------------------------------------------------------------------------------- 1 | charm_name: designate 2 | 3 | smoke_bundles: 4 | - noble-caracal 5 | gate_bundles: 6 | - noble-caracal 7 | dev_bundles: 8 | - noble-caracal 9 | tests: 10 | - zaza.openstack.charm_tests.designate.tests.DesignateTests 11 | 12 | target_deploy_status: 13 | nrpe: 14 | workload-status: blocked 15 | workload-status-message-prefix: "Nagios server not configured or related" 16 | 17 | tests_options: 18 | force_deploy: 19 | # nrpe charm doesn't support jammy+ and needs to be force installed 20 | # https://bugs.launchpad.net/charm-nrpe/+bug/1968008 21 | - noble-caracal 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This is a "source" charm, which is intended to be strictly the top 4 | layer of a built charm. This structure declares that any included 5 | layer assets are not intended to be consumed as a layer from a 6 | functional or design standpoint. 7 | 8 | # Test and Build 9 | 10 | Building, pushing and publishing to the charm store is automated 11 | by CI to ensure consistent flow. Manually building is useful for 12 | development and testing, however. 13 | 14 | ``` 15 | tox -e pep8 16 | tox -e py34 # or py27 or py35 17 | tox -e build 18 | ``` 19 | 20 | # Contact Information 21 | 22 | OFTC IRC: #openstack-charms 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is managed centrally by release-tools and should not be modified 2 | # within individual charm repos. See the 'global' dir contents for available 3 | # choices of *requirements.txt files for OpenStack Charms: 4 | # https://github.com/openstack-charmers/release-tools 5 | # 6 | # NOTE(lourot): This might look like a duplication of test-requirements.txt but 7 | # some tox targets use only test-requirements.txt whereas charm-build uses only 8 | # requirements.txt 9 | 10 | # NOTE: newer versions of cryptography require a Rust compiler to build, 11 | # see 12 | # * https://github.com/openstack-charmers/zaza/issues/421 13 | # * https://mail.python.org/pipermail/cryptography-dev/2021-January/001003.html 14 | # 15 | cryptography<3.4 16 | 17 | git+https://github.com/juju/charm-tools.git 18 | 19 | simplejson 20 | -------------------------------------------------------------------------------- /charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: charm 2 | 3 | parts: 4 | charm: 5 | plugin: reactive 6 | reactive-charm-build-arguments: 7 | - --binary-wheels-from-source 8 | build-packages: 9 | - tox 10 | - git 11 | - python3-dev 12 | - libffi-dev 13 | - libssl-dev 14 | - rustc 15 | - cargo 16 | source: src/ 17 | build-snaps: 18 | - charm/latest/edge 19 | build-environment: 20 | - CHARM_INTERFACES_DIR: $CRAFT_PROJECT_DIR/interfaces/ 21 | - CHARM_LAYERS_DIR: $CRAFT_PROJECT_DIR/layers/ 22 | 23 | base: ubuntu@24.04 24 | platforms: 25 | amd64: 26 | build-on: amd64 27 | build-for: amd64 28 | arm64: 29 | build-on: arm64 30 | build-for: arm64 31 | ppc64el: 32 | build-on: ppc64el 33 | build-for: ppc64el 34 | s390x: 35 | build-on: s390x 36 | build-for: s390x 37 | -------------------------------------------------------------------------------- /src/templates/novarc: -------------------------------------------------------------------------------- 1 | {% if identity_service.api_version == '3' -%} 2 | export OS_AUTH_URL={{ identity_service.auth_protocol }}://{{ identity_service.auth_host }}:{{ identity_service.auth_port }}/v3 3 | export OS_USERNAME={{ identity_service.service_username }} 4 | export OS_PASSWORD={{ identity_service.service_password }} 5 | export OS_USER_DOMAIN_NAME=default 6 | export OS_PROJECT_DOMAIN_NAME=default 7 | export OS_PROJECT_NAME={{ identity_service.service_tenant }} 8 | export OS_REGION_NAME={{ options.region }} 9 | export OS_IDENTITY_API_VERSION=3 10 | export OS_DNS_ENDPOINT={{ options.service_listen_info.designate_api.url }} 11 | export OS_AUTH_VERSION=3 12 | {% else -%} 13 | export OS_AUTH_URL={{ identity_service.auth_protocol }}://{{ identity_service.auth_host }}:{{ identity_service.auth_port }}/v2.0 14 | export OS_TENANT_NAME={{ identity_service.service_tenant }} 15 | export OS_USERNAME={{ identity_service.service_username }} 16 | export OS_PASSWORD={{ identity_service.service_password }} 17 | export OS_REGION_NAME={{ options.region }} 18 | export OS_DNS_ENDPOINT={{ options.service_listen_info.designate_api.url }} 19 | {% endif -%} 20 | -------------------------------------------------------------------------------- /src/metadata.yaml: -------------------------------------------------------------------------------- 1 | name: designate 2 | summary: Designate provides DNSaaS services for OpenStack 3 | maintainer: OpenStack Charmers 4 | description: | 5 | Designate provides DNSaaS services for OpenStack: 6 | * REST API for domain/record management 7 | * Multi-tenant 8 | * Integrated with Keystone for authentication 9 | * Framework in place to integrate with Nova and Neutron notifications 10 | * Support for PowerDNS and Bind9 out of the box 11 | docs: https://discourse.charmhub.io/t/designate-docs-index/11220 12 | tags: 13 | - openstack 14 | - dns 15 | subordinate: false 16 | provides: 17 | dnsaas: 18 | interface: designate 19 | nrpe-external-master: 20 | interface: nrpe-external-master 21 | scope: container 22 | requires: 23 | dns-backend: 24 | interface: bind-rndc 25 | ha: 26 | interface: hacluster 27 | scope: container 28 | coordinator-memcached: 29 | interface: memcache 30 | peers: 31 | cluster: 32 | interface: openstack-ha 33 | resources: 34 | policyd-override: 35 | type: file 36 | filename: policyd-override.zip 37 | description: The policy.d overrides file 38 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is managed centrally by release-tools and should not be modified 2 | # within individual charm repos. See the 'global' dir contents for available 3 | # choices of *requirements.txt files for OpenStack Charms: 4 | # https://github.com/openstack-charmers/release-tools 5 | # 6 | pyparsing<3.0.0 # aodhclient is pinned in zaza and needs pyparsing < 3.0.0, but cffi also needs it, so pin here. 7 | 8 | stestr>=2.2.0 9 | 10 | # Dependency of stestr. Workaround for 11 | # https://github.com/mtreinish/stestr/issues/145 12 | cliff<3.0.0 13 | 14 | requests>=2.18.4 15 | charms.reactive 16 | 17 | mock>=1.2 18 | 19 | nose>=1.3.7 20 | coverage>=3.6 21 | git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack 22 | # 23 | # Revisit for removal / mock improvement: 24 | # 25 | # NOTE(lourot): newer versions of cryptography require a Rust compiler to build, 26 | # see 27 | # * https://github.com/openstack-charmers/zaza/issues/421 28 | # * https://mail.python.org/pipermail/cryptography-dev/2021-January/001003.html 29 | # 30 | netifaces # vault 31 | psycopg2-binary # vault 32 | tenacity # vault 33 | pbr==5.6.0 # vault 34 | cryptography<3.4 # vault, keystone-saml-mellon 35 | lxml # keystone-saml-mellon 36 | hvac # vault, barbican-vault 37 | psutil # cinder-lvm 38 | -------------------------------------------------------------------------------- /unit_tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import sys 16 | from unittest import mock 17 | 18 | sys.path.append('src') 19 | sys.path.append('src/lib') 20 | 21 | # Mock out charmhelpers so that we can test without it. 22 | import charms_openstack.test_mocks # noqa 23 | charms_openstack.test_mocks.mock_charmhelpers() 24 | sys.modules['charmhelpers.core.decorators'] = ( 25 | charms_openstack.test_mocks.charmhelpers.core.decorators) 26 | sys.modules['charmhelpers.contrib.charmsupport.nrpe'] = mock.MagicMock() 27 | 28 | 29 | def _fake_retry(num_retries, base_delay=0, exc_type=Exception): 30 | def _retry_on_exception_inner_1(f): 31 | def _retry_on_exception_inner_2(*args, **kwargs): 32 | return f(*args, **kwargs) 33 | return _retry_on_exception_inner_2 34 | return _retry_on_exception_inner_1 35 | 36 | 37 | mock.patch( 38 | 'charmhelpers.core.decorators.retry_on_exception', 39 | _fake_retry).start() 40 | -------------------------------------------------------------------------------- /src/tox.ini: -------------------------------------------------------------------------------- 1 | # Source charm (with zaza): ./src/tox.ini 2 | # This file is managed centrally by release-tools and should not be modified 3 | # within individual charm repos. See the 'global' dir contents for available 4 | # choices of tox.ini for OpenStack Charms: 5 | # https://github.com/openstack-charmers/release-tools 6 | 7 | [tox] 8 | envlist = pep8 9 | # NOTE: Avoid build/test env pollution by not enabling sitepackages. 10 | sitepackages = False 11 | # NOTE: Avoid false positives by not skipping missing interpreters. 12 | skip_missing_interpreters = False 13 | 14 | [testenv] 15 | # We use tox mainly for virtual environment management for test requirements 16 | # and do not install the charm code as a Python package into that environment. 17 | # Ref: https://tox.wiki/en/latest/config.html#skip_install 18 | skip_install = True 19 | setenv = VIRTUAL_ENV={envdir} 20 | PYTHONHASHSEED=0 21 | allowlist_externals = juju 22 | passenv = 23 | HOME 24 | TERM 25 | CS_* 26 | OS_* 27 | TEST_* 28 | deps = 29 | -c {env:TEST_CONSTRAINTS_FILE:https://raw.githubusercontent.com/openstack-charmers/zaza-openstack-tests/master/constraints/constraints-noble.txt} 30 | -r{toxinidir}/test-requirements.txt 31 | 32 | [testenv:pep8] 33 | basepython = python3 34 | commands = charm-proof 35 | 36 | [testenv:func-noop] 37 | basepython = python3 38 | commands = 39 | functest-run-suite --help 40 | 41 | [testenv:func] 42 | basepython = python3 43 | commands = 44 | functest-run-suite --keep-model 45 | 46 | [testenv:func-smoke] 47 | basepython = python3 48 | commands = 49 | functest-run-suite --keep-model --smoke 50 | 51 | [testenv:func-target] 52 | basepython = python3 53 | commands = 54 | functest-run-suite --keep-model --bundle {posargs} 55 | 56 | [testenv:venv] 57 | commands = {posargs} 58 | -------------------------------------------------------------------------------- /src/templates/mitaka/pools.yaml: -------------------------------------------------------------------------------- 1 | - id: 794ccc2c-d751-44fe-b57f-8894c9f5c842 2 | name: default 3 | description: Pool genergated by Juju 4 | 5 | {% if options.ns_records %} 6 | ns_records: 7 | {% for record in options.ns_records %} 8 | - hostname: {{ record }} 9 | priority: 10 10 | {% endfor %} 11 | {% endif %} 12 | 13 | nameservers: 14 | {% if dns_backend and dns_backend.pool_config %} 15 | {% for slave in dns_backend.pool_config %} 16 | - host: {{ slave.address }} 17 | port: 53 18 | {% endfor %} 19 | {% endif %} 20 | {% if options.pool_config %} 21 | {% for slave in options.pool_config %} 22 | - host: {{ slave.address }} 23 | port: 53 24 | {% endfor %} 25 | {% endif %} 26 | 27 | targets: 28 | {% if dns_backend and dns_backend.pool_config %} 29 | {% for slave in dns_backend.pool_config %} 30 | - type: bind9 31 | masters: 32 | {% for rndc_master_ip in options.rndc_master_ips %} 33 | - host: {{ rndc_master_ip }} 34 | port: 5354 35 | {% endfor %} 36 | options: 37 | host: {{ slave.address }} 38 | rndc_host: {{ slave.address }} 39 | rndc_key_file: {{ slave.rndc_key_file }} 40 | {% endfor %} 41 | {% endif %} 42 | {% if options.pool_config %} 43 | {% for slave in options.pool_config %} 44 | - type: bind9 45 | masters: 46 | {% for rndc_master_ip in cluster.internal_addresses %} 47 | - host: {{ rndc_master_ip }} 48 | port: 5354 49 | {% endfor %} 50 | options: 51 | host: {{ slave.address }} 52 | rndc_host: {{ slave.address }} 53 | rndc_key_file: {{ slave.rndc_key_file }} 54 | {% endfor %} 55 | {% endif %} 56 | 57 | {% if options.also_notifies_hosts %} 58 | also_notifies: 59 | {% for also_notify_host in options.also_notifies_hosts %} 60 | - host: {{ also_notify_host.address }} 61 | port: {{ also_notify_host.port }} 62 | {% endfor %} 63 | # Workaround for https://bugs.launchpad.net/designate/+bug/1758013 64 | {% else %} 65 | also_notifies: [] 66 | {% endif %} 67 | -------------------------------------------------------------------------------- /src/templates/rocky/pools.yaml: -------------------------------------------------------------------------------- 1 | - id: 794ccc2c-d751-44fe-b57f-8894c9f5c842 2 | name: default 3 | description: Pool genergated by Juju 4 | 5 | {% if options.ns_records %} 6 | ns_records: 7 | {% for record in options.ns_records %} 8 | - hostname: {{ record }} 9 | priority: 10 10 | {% endfor %} 11 | {% endif %} 12 | 13 | nameservers: 14 | {% if dns_backend and dns_backend.pool_config %} 15 | {% for slave in dns_backend.pool_config %} 16 | - host: {{ slave.address }} 17 | port: 53 18 | {% endfor %} 19 | {% endif %} 20 | {% if options.pool_config %} 21 | {% for slave in options.pool_config %} 22 | - host: {{ slave.address }} 23 | port: 53 24 | {% endfor %} 25 | {% endif %} 26 | 27 | targets: 28 | {% if dns_backend and dns_backend.pool_config %} 29 | {% for slave in dns_backend.pool_config %} 30 | - type: bind9 31 | masters: 32 | {% for rndc_master_ip in options.rndc_master_ips %} 33 | - host: {{ rndc_master_ip }} 34 | port: 5354 35 | {% endfor %} 36 | options: 37 | host: {{ slave.address }} 38 | rndc_host: {{ slave.address }} 39 | rndc_key_file: {{ slave.rndc_key_file }} 40 | port: 53 41 | {% endfor %} 42 | {% endif %} 43 | {% if options.pool_config %} 44 | {% for slave in options.pool_config %} 45 | - type: bind9 46 | masters: 47 | {% for rndc_master_ip in cluster.internal_addresses %} 48 | - host: {{ rndc_master_ip }} 49 | port: 5354 50 | {% endfor %} 51 | options: 52 | host: {{ slave.address }} 53 | rndc_host: {{ slave.address }} 54 | rndc_key_file: {{ slave.rndc_key_file }} 55 | port: 53 56 | {% endfor %} 57 | {% endif %} 58 | 59 | {% if options.also_notifies_hosts %} 60 | also_notifies: 61 | {% for also_notify_host in options.also_notifies_hosts %} 62 | - host: {{ also_notify_host.address }} 63 | port: {{ also_notify_host.port }} 64 | {% endfor %} 65 | # Workaround for https://bugs.launchpad.net/designate/+bug/1758013 66 | {% else %} 67 | also_notifies: [] 68 | {% endif %} 69 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This charm provides Designate (DNSaaS) for an OpenStack Cloud. 4 | 5 | # Usage 6 | 7 | Designate relies on services from the mysql, rabbitmq-server and keystone 8 | charms: 9 | 10 | juju deploy designate 11 | juju deploy mysql 12 | juju deploy rabbitmq-server 13 | juju deploy keystone 14 | juju deploy memcached 15 | juju add-relation designate memcached 16 | juju add-relation designate mysql 17 | juju add-relation designate rabbitmq-server 18 | juju add-relation designate keystone 19 | 20 | To add support for DNS record auto-generation when Neutron ports and 21 | floating IPs are created the charm needs a relation with neutron-api charm: 22 | 23 | juju deploy neutron-api 24 | juju add-relation designate neutron-api 25 | 26 | The charm needs to store DNS records. This can be achieved by setting the 27 | dns-slave config option or by relating to the designate-bind charm: 28 | 29 | juju deploy designate-bind 30 | juju add-relation designate designate-bind 31 | 32 | For Queens and later, the nameservers config value must be set: 33 | 34 | juju config designate nameservers="ns1.example.com. ns2.example.com." 35 | 36 | ## Policy Overrides 37 | 38 | Policy overrides is an **advanced** feature that allows an operator to override 39 | the default policy of an OpenStack service. The policies that the service 40 | supports, the defaults it implements in its code, and the defaults that a charm 41 | may include should all be clearly understood before proceeding. 42 | 43 | > **Caution**: It is possible to break the system (for tenants and other 44 | services) if policies are incorrectly applied to the service. 45 | 46 | Policy statements are placed in a YAML file. This file (or files) is then (ZIP) 47 | compressed into a single file and used as an application resource. The override 48 | is then enabled via a Boolean charm option. 49 | 50 | Here are the essential commands (filenames are arbitrary): 51 | 52 | zip overrides.zip override-file.yaml 53 | juju attach-resource designate policyd-override=overrides.zip 54 | juju config designate use-policyd-override=true 55 | 56 | See appendix [Policy Overrides][cdg-appendix-n] in the [OpenStack Charms 57 | Deployment Guide][cdg] for a thorough treatment of this feature. 58 | 59 | # Bugs 60 | 61 | Please report bugs on [Launchpad][lp-bugs-charm-designate]. 62 | 63 | For general charm questions refer to the OpenStack [Charm Guide][cg]. 64 | 65 | 66 | 67 | [cg]: https://docs.openstack.org/charm-guide 68 | [cdg]: https://docs.openstack.org/project-deploy-guide/charm-deployment-guide 69 | [cdg-appendix-n]: https://docs.openstack.org/project-deploy-guide/charm-deployment-guide/latest/app-policy-overrides.html 70 | [lp-bugs-charm-designate]: https://bugs.launchpad.net/charm-designate/+filebug 71 | -------------------------------------------------------------------------------- /src/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | designate 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/tests/bundles/noble-caracal.yaml: -------------------------------------------------------------------------------- 1 | variables: 2 | openstack-origin: &openstack-origin distro 3 | 4 | local_overlay_enabled: False 5 | 6 | series: noble 7 | 8 | machines: 9 | '0': 10 | constraints: mem=3072M 11 | '1': 12 | constraints: mem=3072M 13 | '2': 14 | constraints: mem=3072M 15 | '3': 16 | '4': 17 | '5': 18 | '6': 19 | '7': 20 | # Note: memcached is only available on jammy 21 | series: jammy 22 | '8': 23 | 24 | applications: 25 | 26 | keystone-mysql-router: 27 | charm: ch:mysql-router 28 | channel: latest/edge 29 | designate-mysql-router: 30 | charm: ch:mysql-router 31 | channel: latest/edge 32 | neutron-api-mysql-router: 33 | charm: ch:mysql-router 34 | channel: latest/edge 35 | 36 | mysql-innodb-cluster: 37 | charm: ch:mysql-innodb-cluster 38 | num_units: 3 39 | to: 40 | - '0' 41 | - '1' 42 | - '2' 43 | channel: latest/edge 44 | 45 | rabbitmq-server: 46 | charm: ch:rabbitmq-server 47 | num_units: 1 48 | to: 49 | - '3' 50 | channel: latest/edge 51 | 52 | keystone: 53 | charm: ch:keystone 54 | num_units: 1 55 | options: 56 | openstack-origin: *openstack-origin 57 | to: 58 | - '4' 59 | channel: latest/edge 60 | 61 | neutron-api: 62 | charm: ch:neutron-api 63 | num_units: 1 64 | options: 65 | manage-neutron-plugin-legacy-mode: true 66 | openstack-origin: *openstack-origin 67 | to: 68 | - '5' 69 | channel: latest/edge 70 | 71 | designate-bind: 72 | charm: ch:designate-bind 73 | num_units: 1 74 | # NOTE(ajkavanagh) apparently it has no openstack origin! 75 | #options: 76 | #openstack-origin: *openstack-origin 77 | to: 78 | - '6' 79 | channel: latest/edge 80 | 81 | memcached: 82 | charm: ch:memcached 83 | num_units: 1 84 | series: jammy 85 | to: 86 | - '7' 87 | 88 | designate: 89 | charm: ../../../designate_amd64.charm 90 | num_units: 1 91 | options: 92 | nameservers: 'ns1.amuletexample.com. ns2.amuletexample.com.' 93 | openstack-origin: *openstack-origin 94 | to: 95 | - '8' 96 | 97 | nrpe: 98 | charm: ch:nrpe 99 | channel: latest/edge 100 | 101 | relations: 102 | - - 'keystone:shared-db' 103 | - 'keystone-mysql-router:shared-db' 104 | - - 'keystone-mysql-router:db-router' 105 | - 'mysql-innodb-cluster:db-router' 106 | 107 | - - 'designate:shared-db' 108 | - 'designate-mysql-router:shared-db' 109 | - - 'designate-mysql-router:db-router' 110 | - 'mysql-innodb-cluster:db-router' 111 | 112 | - - 'designate:amqp' 113 | - 'rabbitmq-server:amqp' 114 | 115 | - - 'designate:identity-service' 116 | - 'keystone:identity-service' 117 | 118 | - - 'designate:dns-backend' 119 | - 'designate-bind:dns-backend' 120 | 121 | - - 'designate:coordinator-memcached' 122 | - 'memcached:cache' 123 | 124 | - - 'designate:dnsaas' 125 | - 'neutron-api:external-dns' 126 | 127 | - - 'neutron-api:identity-service' 128 | - 'keystone:identity-service' 129 | 130 | - - 'neutron-api:shared-db' 131 | - 'neutron-api-mysql-router:shared-db' 132 | - - 'neutron-api-mysql-router:db-router' 133 | - 'mysql-innodb-cluster:db-router' 134 | 135 | - - 'neutron-api:amqp' 136 | - 'rabbitmq-server:amqp' 137 | 138 | - - 'designate:nrpe-external-master' 139 | - 'nrpe:nrpe-external-master' 140 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Source charm: ./tox.ini 2 | # This file is managed centrally by release-tools and should not be modified 3 | # within individual charm repos. See the 'global' dir contents for available 4 | # choices of tox.ini for OpenStack Charms: 5 | # https://github.com/openstack-charmers/release-tools 6 | 7 | [tox] 8 | envlist = pep8,py3 9 | # NOTE: Avoid build/test env pollution by not enabling sitepackages. 10 | sitepackages = False 11 | # NOTE: Avoid false positives by not skipping missing interpreters. 12 | skip_missing_interpreters = False 13 | 14 | [testenv] 15 | # We use tox mainly for virtual environment management for test requirements 16 | # and do not install the charm code as a Python package into that environment. 17 | # Ref: https://tox.wiki/en/latest/config.html#skip_install 18 | skip_install = True 19 | setenv = VIRTUAL_ENV={envdir} 20 | PYTHONHASHSEED=0 21 | TERM=linux 22 | CHARM_LAYERS_DIR={toxinidir}/layers 23 | CHARM_INTERFACES_DIR={toxinidir}/interfaces 24 | JUJU_REPOSITORY={toxinidir}/build 25 | passenv = 26 | no_proxy 27 | http_proxy 28 | https_proxy 29 | CHARM_INTERFACES_DIR 30 | CHARM_LAYERS_DIR 31 | JUJU_REPOSITORY 32 | allowlist_externals = 33 | charmcraft 34 | bash 35 | tox 36 | deps = 37 | -r{toxinidir}/requirements.txt 38 | 39 | [testenv:build] 40 | basepython = python3 41 | # charmcraft clean is done to ensure that 42 | # `tox -e build` always performs a clean, repeatable build. 43 | # For faster rebuilds during development, 44 | # directly run `charmcraft -v pack && ./rename.sh`. 45 | commands = 46 | charmcraft clean 47 | charmcraft -v pack 48 | charmcraft clean 49 | 50 | [testenv:build-reactive] 51 | basepython = python3 52 | commands = 53 | charm-build --log-level DEBUG --use-lock-file-branches --binary-wheels-from-source -o {toxinidir}/build/builds src {posargs} 54 | 55 | [testenv:add-build-lock-file] 56 | basepython = python3 57 | commands = 58 | charm-build --log-level DEBUG --write-lock-file -o {toxinidir}/build/builds src {posargs} 59 | 60 | [testenv:py3] 61 | basepython = python3 62 | deps = 63 | -c {env:TEST_CONSTRAINTS_FILE:https://raw.githubusercontent.com/openstack-charmers/zaza-openstack-tests/master/constraints/constraints-noble.txt} 64 | -r{toxinidir}/test-requirements.txt 65 | commands = stestr run --slowest {posargs} 66 | 67 | [testenv:py312] 68 | basepython = python3.12 69 | deps = 70 | -c {env:TEST_CONSTRAINTS_FILE:https://raw.githubusercontent.com/openstack-charmers/zaza-openstack-tests/master/constraints/constraints-noble.txt} 71 | -r{toxinidir}/test-requirements.txt 72 | commands = stestr run --slowest {posargs} 73 | 74 | [testenv:py310] 75 | basepython = python3.10 76 | deps = 77 | -c {env:TEST_CONSTRAINTS_FILE:https://raw.githubusercontent.com/openstack-charmers/zaza-openstack-tests/master/constraints/constraints-noble.txt} 78 | -r{toxinidir}/test-requirements.txt 79 | commands = stestr run --slowest {posargs} 80 | 81 | [testenv:pep8] 82 | basepython = python3 83 | deps = flake8==7.1.1 84 | git+https://github.com/juju/charm-tools.git 85 | commands = flake8 {posargs} src unit_tests 86 | 87 | [testenv:cover] 88 | # Technique based heavily upon 89 | # https://github.com/openstack/nova/blob/master/tox.ini 90 | basepython = python3 91 | deps = 92 | -c {env:TEST_CONSTRAINTS_FILE:https://raw.githubusercontent.com/openstack-charmers/zaza-openstack-tests/master/constraints/constraints-noble.txt} 93 | -r{toxinidir}/requirements.txt 94 | -r{toxinidir}/test-requirements.txt 95 | setenv = 96 | {[testenv]setenv} 97 | PYTHON=coverage run 98 | commands = 99 | coverage erase 100 | stestr run --slowest {posargs} 101 | coverage combine 102 | coverage html -d cover 103 | coverage xml -o cover/coverage.xml 104 | coverage report 105 | 106 | [coverage:run] 107 | branch = True 108 | concurrency = multiprocessing 109 | parallel = True 110 | source = 111 | . 112 | omit = 113 | .tox/* 114 | */charmhelpers/* 115 | unit_tests/* 116 | 117 | [testenv:venv] 118 | basepython = python3 119 | commands = {posargs} 120 | 121 | [flake8] 122 | # E402 ignore necessary for path append before sys module import in actions 123 | ignore = E402,W503,W504 124 | -------------------------------------------------------------------------------- /src/reactive/designate_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright 2016 Canonical Ltd 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain 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, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import argparse 18 | import os 19 | import subprocess 20 | 21 | 22 | def display(msg): 23 | print(msg) 24 | 25 | 26 | def run_command(cmd): 27 | os_env = get_environment(os.environ.copy()) 28 | p = subprocess.Popen(cmd, env=os_env, stdout=subprocess.PIPE, 29 | stderr=subprocess.PIPE) 30 | out, err = p.communicate() 31 | if p.returncode != 0: 32 | raise RuntimeError( 33 | "{} failed, status code {} stdout {} stderr {}".format( 34 | cmd, p.returncode, out, err)) 35 | return out, err 36 | 37 | 38 | def get_environment(env): 39 | with open("/root/novarc", "r") as ins: 40 | for line in ins: 41 | k, v = line.replace('export', '').replace(" ", "").split('=') 42 | env[k] = v.strip() 43 | return env 44 | 45 | 46 | def get_server_id(server_name): 47 | servers = get_servers() 48 | if servers.get(server_name): 49 | return servers[server_name]['id'] 50 | 51 | 52 | def display_server_id(server_name): 53 | server_id = get_server_id(server_name) 54 | if server_id: 55 | display(server_id) 56 | 57 | 58 | def get_domain_id(domain_name): 59 | domains = get_domains() 60 | if domains.get(domain_name): 61 | return domains[domain_name]['id'] 62 | 63 | 64 | def display_domain_id(domain_name): 65 | domain_id = get_domain_id(domain_name) 66 | if domain_id: 67 | display(domain_id) 68 | 69 | 70 | def create_server(server_name): 71 | server_id = get_server_id(server_name) 72 | if server_id: 73 | return server_id 74 | cmd = [ 75 | 'designate', 'server-create', 76 | '--name', server_name, 77 | '-f', 'value', 78 | ] 79 | out, err = run_command(cmd) 80 | display(get_server_id(server_name)) 81 | 82 | 83 | def create_domain(domain_name, domain_email): 84 | domain_id = get_domain_id(domain_name) 85 | if domain_id: 86 | return domain_id 87 | cmd = [ 88 | 'designate', 'domain-create', 89 | '--name', domain_name, 90 | '--email', domain_email, 91 | '-f', 'value', 92 | ] 93 | out, err = run_command(cmd) 94 | display(get_domain_id(domain_name)) 95 | 96 | 97 | def delete_domain(domain_name): 98 | domain_id = get_domain_id(domain_name) 99 | if domain_id: 100 | cmd = ['domain-delete', domain_id] 101 | run_command(cmd) 102 | 103 | 104 | def get_domains(): 105 | domains = {} 106 | cmd = ['designate', 'domain-list', '-f', 'value'] 107 | out, err = run_command(cmd) 108 | for line in out.decode('utf8').split('\n'): 109 | values = line.split() 110 | if values: 111 | domains[values[1]] = { 112 | 'id': values[0], 113 | 'serial': values[2], 114 | } 115 | return domains 116 | 117 | 118 | def get_servers(): 119 | servers = {} 120 | cmd = ['designate', 'server-list', '-f', 'value'] 121 | out, err = run_command(cmd) 122 | for line in out.decode('utf8').split('\n'): 123 | values = line.split() 124 | if values: 125 | servers[values[1]] = { 126 | 'id': values[0], 127 | } 128 | return servers 129 | 130 | 131 | def display_domains(): 132 | for domain in get_domains(): 133 | display(domain) 134 | 135 | 136 | def display_servers(): 137 | for server in get_servers(): 138 | display(server) 139 | 140 | 141 | if __name__ == '__main__': 142 | commands = { 143 | 'domain-create': create_domain, 144 | 'server-create': create_server, 145 | 'domain-get': display_domain_id, 146 | 'server-get': display_server_id, 147 | 'domain-delete': delete_domain, 148 | 'domain-list': display_domains, 149 | 'server-list': display_servers, 150 | } 151 | cmd_args = [] 152 | parser = argparse.ArgumentParser(description='Manage designate.') 153 | parser.add_argument('command', 154 | help='One of: {}'.format(', '.join(commands.keys()))) 155 | parser.add_argument('--domain-name', help='Domain Name') 156 | parser.add_argument('--server-name', help='Server Name') 157 | parser.add_argument('--email', help='Email Address') 158 | args = parser.parse_args() 159 | if args.domain_name: 160 | cmd_args.append(args.domain_name) 161 | if args.server_name: 162 | cmd_args.append(args.server_name) 163 | if args.email: 164 | cmd_args.append(args.email) 165 | 166 | if cmd_args: 167 | commands[args.command](*cmd_args) 168 | else: 169 | commands[args.command]() 170 | -------------------------------------------------------------------------------- /unit_tests/test_designate_utils.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | import unittest 3 | 4 | import reactive.designate_utils as dutils 5 | 6 | DOMAIN_LIST = b""" 7 | b78d458c-2a69-47e7-aa40-a1f9ff8809e3 frodo.com. 1467534540 8 | fa5111a7-5659-45c6-a101-525b4259e8f0 bilbo.com. 1467534855 9 | """ 10 | 11 | SERVER_LIST = b""" 12 | 77eee1aa-27fc-49b9-acca-3faf68126530 ns1.www.example.com. 13 | """ 14 | 15 | 16 | class TestDesignateUtils(unittest.TestCase): 17 | 18 | def setUp(self): 19 | self._patches = {} 20 | self._patches_start = {} 21 | 22 | def tearDown(self): 23 | for k, v in self._patches.items(): 24 | v.stop() 25 | setattr(self, k, None) 26 | self._patches = None 27 | self._patches_start = None 28 | 29 | def patch(self, obj, attr, return_value=None): 30 | mocked = mock.patch.object(obj, attr) 31 | self._patches[attr] = mocked 32 | started = mocked.start() 33 | started.return_value = return_value 34 | self._patches_start[attr] = started 35 | setattr(self, attr, started) 36 | 37 | def test_run_command(self): 38 | self.patch(dutils, 'get_environment') 39 | self.patch(dutils.subprocess, 'Popen') 40 | process_mock = mock.Mock() 41 | attrs = { 42 | 'communicate.return_value': ('ouput', 'error'), 43 | 'returncode': 0} 44 | process_mock.configure_mock(**attrs) 45 | self.Popen.return_value = process_mock 46 | self.Popen.returncode.return_value = 0 47 | dutils.run_command(['ls']) 48 | self.Popen.assert_called_once_with( 49 | ['ls'], 50 | env=None, 51 | stderr=-1, 52 | stdout=-1) 53 | 54 | def test_run_command_fail(self): 55 | self.patch(dutils, 'get_environment') 56 | self.patch(dutils.subprocess, 'Popen') 57 | process_mock = mock.Mock() 58 | attrs = { 59 | 'communicate.return_value': ('ouput', 'error'), 60 | 'returncode': 1} 61 | process_mock.configure_mock(**attrs) 62 | self.Popen.return_value = process_mock 63 | self.Popen.returncode.return_value = 0 64 | with self.assertRaises(RuntimeError): 65 | dutils.run_command(['ls']) 66 | 67 | def test_get_environment(self): 68 | text_file_data = '\n'.join(["export a=b", "export c=d"]) 69 | with mock.patch('builtins.open', 70 | mock.mock_open(read_data=text_file_data), 71 | create=True) as m: 72 | m.return_value.__iter__.return_value = text_file_data.splitlines() 73 | with open('filename', 'rU'): 74 | self.assertEqual( 75 | dutils.get_environment({}), 76 | {'a': 'b', 'c': 'd'}) 77 | 78 | def test_get_server_id(self): 79 | self.patch(dutils, 'get_servers') 80 | self.get_servers.return_value = {'server1': {'id': 'servid1'}} 81 | self.assertEqual(dutils.get_server_id('server1'), 'servid1') 82 | self.assertEqual(dutils.get_server_id('server2'), None) 83 | 84 | def test_get_domain_id(self): 85 | self.patch(dutils, 'get_domains') 86 | self.get_domains.return_value = {'domain1': {'id': 'domainid1'}} 87 | self.assertEqual(dutils.get_domain_id('domain1'), 'domainid1') 88 | self.assertEqual(dutils.get_domain_id('domain2'), None) 89 | 90 | def test_create_server(self): 91 | _server_ids = ['servid1', None] 92 | self.patch(dutils, 'get_server_id') 93 | self.patch(dutils, 'display') 94 | self.get_server_id.side_effect = lambda x: _server_ids.pop() 95 | self.patch(dutils, 'run_command') 96 | self.run_command.return_value = ('out', 'err') 97 | dutils.create_server('server1') 98 | cmd = [ 99 | 'designate', 'server-create', 100 | '--name', 'server1', 101 | '-f', 'value', 102 | ] 103 | self.run_command.assert_called_with(cmd) 104 | self.display.assert_called_with('servid1') 105 | 106 | def test_create_domain(self): 107 | _domain_ids = ['domainid1', None] 108 | self.patch(dutils, 'get_domain_id') 109 | self.patch(dutils, 'display') 110 | self.get_domain_id.side_effect = lambda x: _domain_ids.pop() 111 | self.patch(dutils, 'run_command') 112 | self.run_command.return_value = ('out', 'err') 113 | dutils.create_domain('dom1', 'email1') 114 | cmd = [ 115 | 'designate', 'domain-create', 116 | '--name', 'dom1', 117 | '--email', 'email1', 118 | '-f', 'value', 119 | ] 120 | self.run_command.assert_called_with(cmd) 121 | self.display.assert_called_with('domainid1') 122 | 123 | def test_delete_domain(self): 124 | self.patch(dutils, 'get_domain_id', return_value='dom1') 125 | self.patch(dutils, 'run_command') 126 | dutils.delete_domain('dom1') 127 | self.run_command.assert_called_with(['domain-delete', 'dom1']) 128 | 129 | def test_get_domains(self): 130 | self.patch(dutils, 'run_command') 131 | self.run_command.return_value = (DOMAIN_LIST, 'err') 132 | expect = { 133 | 'bilbo.com.': 134 | { 135 | 'id': 'fa5111a7-5659-45c6-a101-525b4259e8f0', 136 | 'serial': '1467534855'}, 137 | 'frodo.com.': 138 | { 139 | 'id': 'b78d458c-2a69-47e7-aa40-a1f9ff8809e3', 140 | 'serial': '1467534540'}} 141 | self.assertEqual(dutils.get_domains(), expect) 142 | self.run_command.assert_called_with( 143 | ['designate', 'domain-list', '-f', 'value']) 144 | 145 | def test_get_servers(self): 146 | self.patch(dutils, 'run_command') 147 | self.run_command.return_value = (SERVER_LIST, 'err') 148 | expect = { 149 | 'ns1.www.example.com.': { 150 | 'id': '77eee1aa-27fc-49b9-acca-3faf68126530'}} 151 | self.assertEqual(dutils.get_servers(), expect) 152 | self.run_command.assert_called_with( 153 | ['designate', 'server-list', '-f', 'value']) 154 | -------------------------------------------------------------------------------- /src/config.yaml: -------------------------------------------------------------------------------- 1 | options: 2 | dns-slaves: 3 | type: string 4 | default: 5 | description: | 6 | List of DNS slaves which will accept addzone/delzone rndc commands from 7 | Designate. List is of the form slave_ip:rndc_port:rndc_key. This should 8 | only be used if DNS servers are outside of Juju control. Using the 9 | designate-bind charm is the prefered approach. 10 | nova-domain: 11 | type: string 12 | default: 13 | description: | 14 | Domain to add records for new instances to 15 | (NOTE: This option is obsolete starting from OpenStack Mitaka release) 16 | nova-domain-email: 17 | type: string 18 | default: 19 | description: | 20 | Email address of the person responsible for the domain. 21 | (NOTE: This option is obsolete starting from OpenStack Mitaka release) 22 | nameservers: 23 | type: string 24 | default: 25 | description: | 26 | Space delimited list of nameservers. These are the nameservers that have 27 | been provided to the domain registrar in order to delegate the domain to 28 | Designate. e.g. "ns1.example.com. ns2.example.com." 29 | This config value is required for Queens and later. 30 | neutron-domain: 31 | type: string 32 | default: 33 | description: | 34 | Domain to add floating IP records to. 35 | (NOTE: This option is obsolete starting from OpenStack Mitaka release) 36 | neutron-domain-email: 37 | type: string 38 | default: 39 | description: | 40 | Email address of the person responsible for the domain. 41 | (NOTE: This option is obsolete starting from OpenStack Mitaka release) 42 | neutron-record-format: 43 | type: string 44 | default: '%(octet0)s-%(octet1)s-%(octet2)s-%(octet3)s.%(zone)s' 45 | description: | 46 | Format of floating IP global records. 47 | (NOTE: This option is obsolete starting from OpenStack Mitaka release) 48 | neutron-record-formatv6: 49 | type: string 50 | default: '%(hostname)s.%(tenant_id)s.%(zone)s' 51 | description: | 52 | Format of floating IPv6 global records. 53 | (NOTE: This option is obsolete starting from OpenStack Mitaka release) 54 | nova-record-format: 55 | type: string 56 | default: '%(hostname)s.%(tenant_id)s.%(zone)s' 57 | description: | 58 | Format of floating IP global records. 59 | (NOTE: This option is obsolete starting from OpenStack Mitaka release) 60 | nova-record-formatv6: 61 | type: string 62 | default: '%(hostname)s.%(tenant_id)s.%(zone)s' 63 | description: | 64 | Format of floating IPv6 global records. 65 | (NOTE: This option is obsolete starting from OpenStack Mitaka release) 66 | also-notifies: 67 | type: string 68 | default: 69 | description: | 70 | Space delimited list of DNS servers which should be notified on every 71 | zone change in addition to the backend servers. List is of the form 72 | also_notify_ip:also_notify_port 73 | enable-admin-api: 74 | type: boolean 75 | default: false 76 | description: Enables experimental admin API for Designate. 77 | nrpe-nameserver-check-host: 78 | type: string 79 | default: canonical.com 80 | description: | 81 | The host to use for the NRPE nameserver checks. This is useful if you 82 | are deploying in a constrained environment where some domain are not resolvable, 83 | allowing you to use a known resolvable domain. To disable the check set this 84 | configuration option to an empty string. 85 | default-soa-expire: 86 | type: int 87 | default: 86400 88 | description: | 89 | Default SOA expire value (in seconds) to specify how long a 90 | secondary will still treat its copy of the zone data as valid if 91 | it can't contact the primary. 92 | default-soa-minimum: 93 | type: int 94 | default: 95 | description: | 96 | Default SOA minimum value (in seconds) how long should cache a negative 97 | response. 98 | default-soa-refresh-min: 99 | type: int 100 | default: 101 | description: | 102 | SOA refresh indicates the time (in seconds) when the slave will try to 103 | refresh the zone from the master, Designate randomizes the refresh time 104 | using `default-soa-refresh-min` and `default-soa-refresh-max` as lower 105 | and upper boundaries respectively. 106 | default-soa-refresh-max: 107 | type: int 108 | default: 109 | description: | 110 | SOA refresh indicates the time (in seconds) when the slave will try to 111 | refresh the zone from the master, Designate randomizes the refresh time 112 | using `default-soa-refresh-min` and `default-soa-refresh-max` as lower 113 | and upper boundaries respectively. 114 | default-soa-retry: 115 | type: int 116 | default: 117 | description: | 118 | Defines the time (in seconds) between retries for the SOA record if the 119 | slave (secondary) fails to contact the master when refresh has expired. 120 | managed-resource-email: 121 | type: string 122 | default: hostmaster@example.com 123 | description: | 124 | Define a email to use for managed resources like domains created by the 125 | FloatingIP. 126 | default-ttl: 127 | type: int 128 | default: 129 | description: | 130 | Default TTL (in seconds) for newly created zones. 131 | use-policyd-override: 132 | type: boolean 133 | default: False 134 | description: | 135 | If True then use the resource file named 'policyd-override' to install 136 | override YAML files in the service's policy.d directory. The resource 137 | file should be a ZIP file containing at least one yaml file with a .yaml 138 | or .yml extension. If False then remove the overrides. 139 | zone-purge-time-threshold: 140 | type: int 141 | default: 3600 142 | description: | 143 | Default for how old deleted zones should be (deleted_at) to be 144 | purged, in seconds. 145 | nagios_context: 146 | default: "juju" 147 | type: string 148 | description: | 149 | A string that will be prepended to instance name to set the host name 150 | in nagios. So for instance the hostname would be something like: 151 | juju-myservice-0 152 | If you're running multiple environments with the same services in them 153 | this allows you to differentiate between them. 154 | nagios_servicegroups: 155 | default: "" 156 | type: string 157 | description: | 158 | A comma-separated list of nagios servicegroups. If left empty, the 159 | nagios_context will be used as the servicegroup. 160 | managed-resource-tenant-id: 161 | type: string 162 | default: 163 | description: | 164 | Set the project ID to own all managed resources like auto-created records etc. 165 | openstack-origin: 166 | default: caracal 167 | -------------------------------------------------------------------------------- /src/templates/caracal/designate.conf: -------------------------------------------------------------------------------- 1 | # Caracal 2 | [DEFAULT] 3 | # Where an option is commented out, but filled in this shows the default 4 | # value of that optiona aaa 5 | 6 | ######################## 7 | ## General Configuration 8 | ######################## 9 | # Show more verbose log output (sets INFO log level output) 10 | verbose = {{ options.verbose }} 11 | 12 | # Show debugging output in logs (sets DEBUG log level output) 13 | debug = {{ options.debug }} 14 | 15 | # Top-level directory for maintaining designate's state 16 | #state_path = /var/lib/designate 17 | 18 | # Log Configuration 19 | #log_config = None 20 | 21 | # Log directory 22 | #logdir = /var/log/designate 23 | 24 | # Use "sudo designate-rootwrap /etc/designate/rootwrap.conf" to use the real 25 | # root filter facility. 26 | # Change to "sudo" to skip the filtering and just run the comand directly 27 | #root_helper = sudo designate-rootwrap /etc/designate/rootwrap.conf 28 | 29 | # Which networking API to use, Defaults to neutron 30 | #network_api = neutron 31 | 32 | {%- if options.default_ttl %} 33 | # TTL Value (integer value) 34 | default_ttl = {{ options.default_ttl }} 35 | {%- endif %} 36 | {%- if options.default_soa_refresh_min %} 37 | # SOA refresh-min value (integer value) 38 | default_soa_refresh_min = {{ options.default_soa_refresh_min }} 39 | {%- endif %} 40 | {%- if options.default_soa_refresh_max %} 41 | # SOA max value (integer value) 42 | default_soa_refresh_max = {{ options.default_soa_refresh_max }} 43 | {%- endif %} 44 | {%- if options.default_soa_retry %} 45 | # SOA retry (integer value) 46 | default_soa_retry = {{ options.default_soa_retry }} 47 | {%- endif %} 48 | {%- if options.default_soa_minimum %} 49 | # SOA minimum value (integer value) 50 | default_soa_minimum = {{ options.default_soa_minimum }} 51 | {%- endif %} 52 | # SOA expire (integer value) 53 | default_soa_expire = {{ options.default_soa_expire }} 54 | 55 | {% include "parts/section-transport-url" %} 56 | 57 | #----------------------- 58 | # RabbitMQ Config 59 | #----------------------- 60 | {% include "parts/section-oslo-messaging-rabbit" %} 61 | 62 | ######################## 63 | ## Service Configuration 64 | ######################## 65 | #----------------------- 66 | # Central Service 67 | #----------------------- 68 | [service:central] 69 | # Number of central worker processes to spawn 70 | workers = {{ options.workers }} 71 | 72 | # Number of central greenthreads to spawn 73 | #threads = 1000 74 | 75 | # Maximum domain name length 76 | #max_domain_name_len = 255 77 | 78 | # Maximum recordset name length 79 | #max_recordset_name_len = 255 80 | 81 | # Minimum TTL 82 | #min_ttl = None 83 | 84 | # The name of the default pool 85 | #default_pool_id = '794ccc2c-d751-44fe-b57f-8894c9f5c842' 86 | 87 | ## Managed resources settings 88 | 89 | # Email to use for managed resources like domains created by the FloatingIP API 90 | managed_resource_email = {{ options.managed_resource_email }} 91 | 92 | {%- if options.managed_resource_tenant_id %} 93 | # Tenant ID to own all managed resources - like auto-created records etc. 94 | managed_resource_tenant_id = {{ options.managed_resource_tenant_id }} 95 | {%- endif %} 96 | 97 | #----------------------- 98 | # API Service 99 | #----------------------- 100 | [service:api] 101 | # Number of api worker processes to spawn 102 | #workers = None 103 | 104 | # Number of api greenthreads to spawn 105 | #threads = 1000 106 | 107 | # Enable host request headers 108 | enable_host_header = true 109 | 110 | # The base uri used in responses 111 | api_base_uri = '{{ options.external_endpoints.designate_api.url }}' 112 | 113 | # API host:port pairs to listen on (list value) 114 | # NOTE:tinwood - Listen on every interface; fix for BUG #1734156 115 | listen = 0.0.0.0:{{ options.service_listen_info.designate_api.port }} 116 | 117 | # Maximum line size of message headers to be accepted. max_header_line may 118 | # need to be increased when using large tokens (typically those generated by 119 | # the Keystone v3 API with big service catalogs). 120 | #max_header_line = 16384 121 | 122 | # Authentication strategy to use - can be either "noauth" or "keystone" 123 | #auth_strategy = keystone 124 | 125 | # Enable Version 1 API (deprecated) 126 | enable_api_v1 = True 127 | 128 | # Enabled API Version 1 extensions 129 | # Can be one or more of : diagnostics, quotas, reports, sync, touch 130 | #enabled_extensions_v1 = 131 | enabled_extensions_v1 = sync, touch 132 | 133 | # Enable Version 2 API 134 | enable_api_v2 = True 135 | 136 | # Enabled API Version 2 extensions 137 | #enabled_extensions_v2 = 138 | 139 | # Default per-page limit for the V2 API, a value of None means show all results 140 | # by default 141 | #default_limit_v2 = 20 142 | 143 | # Max page size in the V2 API 144 | #max_limit_v2 = 1000 145 | 146 | # Enable Admin API (experimental) 147 | #enable_api_admin = False 148 | enable_api_admin = {{ options.enable_admin_api }} 149 | 150 | # Enabled Admin API extensions 151 | # Can be one or more of : reports, quotas, counts, tenants, zones 152 | # zone export is in zones extension 153 | #enabled_extensions_admin = 154 | {% if options.enable_admin_api -%} 155 | enabled_extensions_admin = reports, quotas, counts, tenants, zones 156 | {%- endif %} 157 | 158 | # Default per-page limit for the Admin API, a value of None means show all results 159 | # by default 160 | #default_limit_admin = 20 161 | 162 | # Max page size in the Admin API 163 | #max_limit_admin = 1000 164 | 165 | # Show the pecan HTML based debug interface (v2 only) 166 | # This is only useful for development, and WILL break python-designateclient 167 | # if an error occurs 168 | #pecan_debug = False 169 | 170 | #----------------------- 171 | # Keystone Middleware 172 | #----------------------- 173 | {% include "parts/section-keystone-authtoken" %} 174 | 175 | #----------------------- 176 | # Sink Service 177 | #----------------------- 178 | [service:sink] 179 | # List of notification handlers to enable, configuration of these needs to 180 | # correspond to a [handler:my_driver] section below or else in the config 181 | # Can be one or more of : nova_fixed, neutron_floatingip 182 | enabled_notification_handlers = {{ options.notification_handlers }} 183 | 184 | #----------------------- 185 | # mDNS Service 186 | #----------------------- 187 | [service:mdns] 188 | # Number of mdns worker processes to spawn 189 | #workers = None 190 | 191 | # Number of mdns greenthreads to spawn 192 | #threads = 1000 193 | 194 | # mDNS Bind Host 195 | #host = 0.0.0.0 196 | 197 | # mDNS Port Number 198 | #port = 5354 199 | 200 | # mDNS TCP Backlog 201 | #tcp_backlog = 100 202 | 203 | # mDNS TCP Receive Timeout 204 | #tcp_recv_timeout = 0.5 205 | 206 | # Enforce all incoming queries (including AXFR) are TSIG signed 207 | #query_enforce_tsig = False 208 | 209 | # Send all traffic over TCP 210 | #all_tcp = False 211 | 212 | # Maximum message size to emit 213 | #max_message_size = 65535 214 | 215 | #----------------------- 216 | # Worker Service 217 | #----------------------- 218 | [service:worker] 219 | enabled = True 220 | 221 | ################################### 222 | ## Pool Manager Cache Configuration 223 | ################################### 224 | #----------------------- 225 | # SQLAlchemy Pool Manager Cache 226 | #----------------------- 227 | [pool_manager_cache:sqlalchemy] 228 | connection = {{ shared_db.designate_pool_uri }} 229 | #connection = sqlite:///$state_path/designate_pool_manager.sqlite 230 | #connection_debug = 100 231 | #connection_trace = False 232 | #sqlite_synchronous = True 233 | #idle_timeout = 3600 234 | #max_retries = 10 235 | #retry_interval = 10 236 | 237 | #----------------------- 238 | # Memcache Pool Manager Cache 239 | #----------------------- 240 | [pool_manager_cache:memcache] 241 | #memcached_servers = None 242 | #expiration = 3600 243 | 244 | 245 | ############## 246 | ## Network API 247 | ############## 248 | [network_api:neutron] 249 | # Comma separated list of values, formatted "|" 250 | #endpoints = RegionOne|http://localhost:9696 251 | #endpoint_type = publicURL 252 | #timeout = 30 253 | #admin_username = designate 254 | #admin_password = designate 255 | #admin_tenant_name = designate 256 | #auth_url = http://localhost:35357/v2.0 257 | #insecure = False 258 | #auth_strategy = keystone 259 | #ca_certificates_file = 260 | 261 | ######################## 262 | ## Storage Configuration 263 | ######################## 264 | #----------------------- 265 | # SQLAlchemy Storage 266 | #----------------------- 267 | [storage:sqlalchemy] 268 | # Database connection string - to configure options for a given implementation 269 | # like sqlalchemy or other see below 270 | #connection = sqlite:///$state_path/designate.sqlite 271 | connection = {{ shared_db.designate_uri }} 272 | #connection_debug = 0 273 | #connection_trace = False 274 | #sqlite_synchronous = True 275 | #idle_timeout = 3600 276 | #max_retries = 10 277 | #retry_interval = 10 278 | 279 | ######################## 280 | ## Handler Configuration 281 | ######################## 282 | #----------------------- 283 | # Nova Fixed Handler 284 | #----------------------- 285 | #format = '%(hostname)s.%(domain)s' 286 | 287 | ############################# 288 | ## Agent Backend Configuration 289 | ############################# 290 | [backend:agent:bind9] 291 | #rndc_config_file = /etc/rndc.conf 292 | #rndc_key_file = /etc/rndc.key 293 | #zone_file_path = $state_path/zones 294 | #query_destination = 127.0.0.1 295 | # 296 | [backend:agent:denominator] 297 | #name = dynect 298 | #config_file = /etc/denominator.conf 299 | 300 | ######################## 301 | ## Library Configuration 302 | ######################## 303 | [oslo_concurrency] 304 | # Path for Oslo Concurrency to store lock files, defaults to the value 305 | # of the state_path setting. 306 | #lock_path = $state_path 307 | 308 | ######################## 309 | ## Coordination 310 | ######################## 311 | [coordination] 312 | {% if coordinator_memcached.url -%} 313 | backend_url = {{ coordinator_memcached.url }} 314 | {%- endif %} 315 | 316 | ######################## 317 | ## Hook Points 318 | ######################## 319 | # Hook Points are enabled when added to the config and there has been 320 | # a package that provides the corresponding named designate.hook_point 321 | # entry point. 322 | 323 | # [hook_point:name_of_hook_point] 324 | # some_param_for_hook = 42 325 | # Hooks can be disabled in the config 326 | # enabled = False 327 | 328 | # Hook can also be applied to the import path when the hook has not 329 | # been given an explicit name. The name is created from the hook 330 | # target function / method: 331 | # 332 | # name = '%s.%s' % (func.__module__, func.__name__) 333 | 334 | # [hook_point:designate.api.v2.controllers.zones.get_one] 335 | 336 | {% include "parts/section-oslo-middleware" %} 337 | 338 | [producer_task:zone_purge] 339 | 340 | # How old deleted records should be (deleted_at) to be purged, in seconds 341 | time_threshold = {{ options.zone_purge_time_threshold }} 342 | -------------------------------------------------------------------------------- /src/templates/rocky/designate.conf: -------------------------------------------------------------------------------- 1 | # Rocky 2 | [DEFAULT] 3 | # Where an option is commented out, but filled in this shows the default 4 | # value of that optiona aaa 5 | 6 | ######################## 7 | ## General Configuration 8 | ######################## 9 | # Show more verbose log output (sets INFO log level output) 10 | verbose = {{ options.verbose }} 11 | 12 | # Show debugging output in logs (sets DEBUG log level output) 13 | debug = {{ options.debug }} 14 | 15 | # Top-level directory for maintaining designate's state 16 | #state_path = /var/lib/designate 17 | 18 | # Log Configuration 19 | #log_config = None 20 | 21 | # Log directory 22 | #logdir = /var/log/designate 23 | 24 | # Use "sudo designate-rootwrap /etc/designate/rootwrap.conf" to use the real 25 | # root filter facility. 26 | # Change to "sudo" to skip the filtering and just run the comand directly 27 | #root_helper = sudo designate-rootwrap /etc/designate/rootwrap.conf 28 | 29 | # Which networking API to use, Defaults to neutron 30 | #network_api = neutron 31 | 32 | {%- if options.default_ttl %} 33 | # TTL Value (integer value) 34 | default_ttl = {{ options.default_ttl }} 35 | {%- endif %} 36 | {%- if options.default_soa_refresh_min %} 37 | # SOA refresh-min value (integer value) 38 | default_soa_refresh_min = {{ options.default_soa_refresh_min }} 39 | {%- endif %} 40 | {%- if options.default_soa_refresh_max %} 41 | # SOA max value (integer value) 42 | default_soa_refresh_max = {{ options.default_soa_refresh_max }} 43 | {%- endif %} 44 | {%- if options.default_soa_retry %} 45 | # SOA retry (integer value) 46 | default_soa_retry = {{ options.default_soa_retry }} 47 | {%- endif %} 48 | {%- if options.default_soa_minimum %} 49 | # SOA minimum value (integer value) 50 | default_soa_minimum = {{ options.default_soa_minimum }} 51 | {%- endif %} 52 | # SOA expire (integer value) 53 | default_soa_expire = {{ options.default_soa_expire }} 54 | 55 | {% include "parts/section-transport-url" %} 56 | 57 | #----------------------- 58 | # RabbitMQ Config 59 | #----------------------- 60 | {% include "parts/section-oslo-messaging-rabbit" %} 61 | 62 | ######################## 63 | ## Service Configuration 64 | ######################## 65 | #----------------------- 66 | # Central Service 67 | #----------------------- 68 | [service:central] 69 | # Number of central worker processes to spawn 70 | workers = {{ options.workers }} 71 | 72 | # Number of central greenthreads to spawn 73 | #threads = 1000 74 | 75 | # Maximum domain name length 76 | #max_domain_name_len = 255 77 | 78 | # Maximum recordset name length 79 | #max_recordset_name_len = 255 80 | 81 | # Minimum TTL 82 | #min_ttl = None 83 | 84 | # The name of the default pool 85 | #default_pool_id = '794ccc2c-d751-44fe-b57f-8894c9f5c842' 86 | 87 | ## Managed resources settings 88 | 89 | # Email to use for managed resources like domains created by the FloatingIP API 90 | managed_resource_email = {{ options.managed_resource_email }} 91 | 92 | {%- if options.managed_resource_tenant_id %} 93 | # Tenant ID to own all managed resources - like auto-created records etc. 94 | managed_resource_tenant_id = {{ options.managed_resource_tenant_id }} 95 | {%- endif %} 96 | 97 | #----------------------- 98 | # API Service 99 | #----------------------- 100 | [service:api] 101 | # Number of api worker processes to spawn 102 | #workers = None 103 | 104 | # Number of api greenthreads to spawn 105 | #threads = 1000 106 | 107 | # Enable host request headers 108 | enable_host_header = true 109 | 110 | # The base uri used in responses 111 | api_base_uri = '{{ options.external_endpoints.designate_api.url }}' 112 | 113 | # API host:port pairs to listen on (list value) 114 | # NOTE:tinwood - Listen on every interface; fix for BUG #1734156 115 | listen = 0.0.0.0:{{ options.service_listen_info.designate_api.port }} 116 | 117 | # Maximum line size of message headers to be accepted. max_header_line may 118 | # need to be increased when using large tokens (typically those generated by 119 | # the Keystone v3 API with big service catalogs). 120 | #max_header_line = 16384 121 | 122 | # Authentication strategy to use - can be either "noauth" or "keystone" 123 | #auth_strategy = keystone 124 | 125 | # Enable Version 1 API (deprecated) 126 | enable_api_v1 = True 127 | 128 | # Enabled API Version 1 extensions 129 | # Can be one or more of : diagnostics, quotas, reports, sync, touch 130 | #enabled_extensions_v1 = 131 | enabled_extensions_v1 = sync, touch 132 | 133 | # Enable Version 2 API 134 | enable_api_v2 = True 135 | 136 | # Enabled API Version 2 extensions 137 | #enabled_extensions_v2 = 138 | 139 | # Default per-page limit for the V2 API, a value of None means show all results 140 | # by default 141 | #default_limit_v2 = 20 142 | 143 | # Max page size in the V2 API 144 | #max_limit_v2 = 1000 145 | 146 | # Enable Admin API (experimental) 147 | #enable_api_admin = False 148 | enable_api_admin = {{ options.enable_admin_api }} 149 | 150 | # Enabled Admin API extensions 151 | # Can be one or more of : reports, quotas, counts, tenants, zones 152 | # zone export is in zones extension 153 | #enabled_extensions_admin = 154 | {% if options.enable_admin_api -%} 155 | enabled_extensions_admin = reports, quotas, counts, tenants, zones 156 | {%- endif %} 157 | 158 | # Default per-page limit for the Admin API, a value of None means show all results 159 | # by default 160 | #default_limit_admin = 20 161 | 162 | # Max page size in the Admin API 163 | #max_limit_admin = 1000 164 | 165 | # Show the pecan HTML based debug interface (v2 only) 166 | # This is only useful for development, and WILL break python-designateclient 167 | # if an error occurs 168 | #pecan_debug = False 169 | 170 | #----------------------- 171 | # Keystone Middleware 172 | #----------------------- 173 | {% include "parts/section-keystone-authtoken" %} 174 | 175 | #----------------------- 176 | # Sink Service 177 | #----------------------- 178 | [service:sink] 179 | # List of notification handlers to enable, configuration of these needs to 180 | # correspond to a [handler:my_driver] section below or else in the config 181 | # Can be one or more of : nova_fixed, neutron_floatingip 182 | enabled_notification_handlers = {{ options.notification_handlers }} 183 | 184 | #----------------------- 185 | # mDNS Service 186 | #----------------------- 187 | [service:mdns] 188 | # Number of mdns worker processes to spawn 189 | #workers = None 190 | 191 | # Number of mdns greenthreads to spawn 192 | #threads = 1000 193 | 194 | # mDNS Bind Host 195 | #host = 0.0.0.0 196 | 197 | # mDNS Port Number 198 | #port = 5354 199 | 200 | # mDNS TCP Backlog 201 | #tcp_backlog = 100 202 | 203 | # mDNS TCP Receive Timeout 204 | #tcp_recv_timeout = 0.5 205 | 206 | # Enforce all incoming queries (including AXFR) are TSIG signed 207 | #query_enforce_tsig = False 208 | 209 | # Send all traffic over TCP 210 | #all_tcp = False 211 | 212 | # Maximum message size to emit 213 | #max_message_size = 65535 214 | 215 | #----------------------- 216 | # Agent Service 217 | #----------------------- 218 | [service:agent] 219 | #workers = None 220 | #host = 0.0.0.0 221 | #port = 5358 222 | #tcp_backlog = 100 223 | #allow_notify = 127.0.0.1 224 | #masters = 127.0.0.1:5354 225 | #backend_driver = fake 226 | #transfer_source = None 227 | #notify_delay = 0 228 | 229 | #----------------------- 230 | # Worker Service 231 | #----------------------- 232 | [service:worker] 233 | enabled = True 234 | 235 | ################################### 236 | ## Pool Manager Cache Configuration 237 | ################################### 238 | #----------------------- 239 | # SQLAlchemy Pool Manager Cache 240 | #----------------------- 241 | [pool_manager_cache:sqlalchemy] 242 | connection = {{ shared_db.designate_pool_uri }} 243 | #connection = sqlite:///$state_path/designate_pool_manager.sqlite 244 | #connection_debug = 100 245 | #connection_trace = False 246 | #sqlite_synchronous = True 247 | #idle_timeout = 3600 248 | #max_retries = 10 249 | #retry_interval = 10 250 | 251 | #----------------------- 252 | # Memcache Pool Manager Cache 253 | #----------------------- 254 | [pool_manager_cache:memcache] 255 | #memcached_servers = None 256 | #expiration = 3600 257 | 258 | 259 | ############## 260 | ## Network API 261 | ############## 262 | [network_api:neutron] 263 | # Comma separated list of values, formatted "|" 264 | #endpoints = RegionOne|http://localhost:9696 265 | #endpoint_type = publicURL 266 | #timeout = 30 267 | #admin_username = designate 268 | #admin_password = designate 269 | #admin_tenant_name = designate 270 | #auth_url = http://localhost:35357/v2.0 271 | #insecure = False 272 | #auth_strategy = keystone 273 | #ca_certificates_file = 274 | 275 | ######################## 276 | ## Storage Configuration 277 | ######################## 278 | #----------------------- 279 | # SQLAlchemy Storage 280 | #----------------------- 281 | [storage:sqlalchemy] 282 | # Database connection string - to configure options for a given implementation 283 | # like sqlalchemy or other see below 284 | #connection = sqlite:///$state_path/designate.sqlite 285 | connection = {{ shared_db.designate_uri }} 286 | #connection_debug = 0 287 | #connection_trace = False 288 | #sqlite_synchronous = True 289 | #idle_timeout = 3600 290 | #max_retries = 10 291 | #retry_interval = 10 292 | 293 | ######################## 294 | ## Handler Configuration 295 | ######################## 296 | #----------------------- 297 | # Nova Fixed Handler 298 | #----------------------- 299 | #format = '%(hostname)s.%(domain)s' 300 | 301 | ############################# 302 | ## Agent Backend Configuration 303 | ############################# 304 | [backend:agent:bind9] 305 | #rndc_config_file = /etc/rndc.conf 306 | #rndc_key_file = /etc/rndc.key 307 | #zone_file_path = $state_path/zones 308 | #query_destination = 127.0.0.1 309 | # 310 | [backend:agent:denominator] 311 | #name = dynect 312 | #config_file = /etc/denominator.conf 313 | 314 | ######################## 315 | ## Library Configuration 316 | ######################## 317 | [oslo_concurrency] 318 | # Path for Oslo Concurrency to store lock files, defaults to the value 319 | # of the state_path setting. 320 | #lock_path = $state_path 321 | 322 | ######################## 323 | ## Coordination 324 | ######################## 325 | [coordination] 326 | {% if coordinator_memcached.url -%} 327 | backend_url = {{ coordinator_memcached.url }} 328 | {%- endif %} 329 | 330 | ######################## 331 | ## Hook Points 332 | ######################## 333 | # Hook Points are enabled when added to the config and there has been 334 | # a package that provides the corresponding named designate.hook_point 335 | # entry point. 336 | 337 | # [hook_point:name_of_hook_point] 338 | # some_param_for_hook = 42 339 | # Hooks can be disabled in the config 340 | # enabled = False 341 | 342 | # Hook can also be applied to the import path when the hook has not 343 | # been given an explicit name. The name is created from the hook 344 | # target function / method: 345 | # 346 | # name = '%s.%s' % (func.__module__, func.__name__) 347 | 348 | # [hook_point:designate.api.v2.controllers.zones.get_one] 349 | 350 | {% include "parts/section-oslo-middleware" %} 351 | 352 | [producer_task:zone_purge] 353 | 354 | # How old deleted records should be (deleted_at) to be purged, in seconds 355 | time_threshold = {{ options.zone_purge_time_threshold }} 356 | -------------------------------------------------------------------------------- /unit_tests/test_designate_handlers.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | import reactive.designate_handlers as handlers 4 | 5 | import charms_openstack.test_utils as test_utils 6 | 7 | 8 | class TestRegisteredHooks(test_utils.TestRegisteredHooks): 9 | 10 | def test_hooks(self): 11 | # test that the hooks actually registered the relation expressions that 12 | # are meaningful for this interface: this is to handle regressions. 13 | # The keys are the function names that the hook attaches to. 14 | all_interfaces = ( 15 | 'dns-config.available', 16 | 'shared-db.available', 17 | 'identity-service.available', 18 | 'coordinator-memcached.available', 19 | 'amqp.available') 20 | hook_set = { 21 | 'when': { 22 | 'setup_amqp_req': ('amqp.connected', ), 23 | 'setup_database': ('shared-db.connected', ), 24 | 'maybe_setup_endpoint': ('identity-service.connected', ), 25 | 'start_designate_services': ('config.rendered', 26 | 'base-config.rendered', ), 27 | 'expose_rndc_address': ('cluster.connected', ), 28 | 'config_rendered': ('base-config.rendered', ), 29 | 'configure_ssl': ('identity-service.available', ), 30 | 'config_changed': ('config.changed', ), 31 | 'cluster_connected': ('ha.connected', ), 32 | 'create_servers_and_domains': ( 33 | all_interfaces + ('base-config.rendered', 'db.synched')), 34 | 'configure_designate_full': ( 35 | all_interfaces + ( 36 | 'db.synched', 'pool-manager-cache.synched')), 37 | 'run_db_migration': ( 38 | all_interfaces + ('base-config.rendered', )), 39 | 'sync_pool_manager_cache': ( 40 | all_interfaces + ('base-config.rendered', )), 41 | 'configure_designate_basic': all_interfaces, 42 | 'expose_endpoint': ('dnsaas.connected', ), 43 | 'remote_pools_updated': ( 44 | 'leadership.changed.pool-yaml-hash', ), 45 | 'reset_shared_db': ('shared-db.setup', ), 46 | 'configure_nrpe': ('base-config.rendered', ), 47 | 'configure_dns_backend_rndc_keys': ( 48 | all_interfaces + ('dns-backend.available', 'db.synched')), 49 | }, 50 | 'when_not': { 51 | 'setup_amqp_req': ('is-update-status-hook', ), 52 | 'setup_database': ('shared-db.setup', ), 53 | 'config_rendered': ('config.rendered', ), 54 | 'install_packages': ('installed', ), 55 | 'run_db_migration': ('db.synched', ), 56 | 'sync_pool_manager_cache': ('pool-manager-cache.synched', ), 57 | 'configure_designate_basic': ('base-config.rendered', ), 58 | 'create_servers_and_domains': ('domains.created', ), 59 | 'run_assess_status_on_every_hook': ( 60 | 'dont-set-assess-status', ), 61 | 'reset_shared_db': ('shared-db.connected', ), 62 | 'configure_nrpe': ('is-update-status-hook', ), 63 | 'set_dns_config_available': ('is-update-status-hook', ), 64 | 'start_designate_services': ('is-update-status-hook', ), 65 | 'maybe_setup_endpoint': ('is-update-status-hook', ), 66 | 'expose_endpoint': ('is-update-status-hook', ), 67 | 'configure_designate_full': ('is-update-status-hook', ), 68 | 'expose_rndc_address': ('is-update-status-hook', ), 69 | 'cluster_connected': ('is-update-status-hook', ), 70 | 'configure_dns_backend_rndc_keys': ('is-update-status-hook', ), 71 | }, 72 | 'when_any': { 73 | 'set_dns_config_available': ( 74 | 'dns-slaves-config-valid', 'dns-backend.available', ), 75 | 'configure_nrpe': ( 76 | 'config.changed.nagios_context', 77 | 'config.changed.nagios_servicegroups', 78 | 'config.changed.nameservers', 79 | 'config.changed.nrpe-nameserver-check-host', 80 | 'endpoint.nrpe-external-master.changed', 81 | 'nrpe-external-master.available', 82 | ), 83 | }, 84 | 'when_none': { 85 | 'clear_dns_config_available': ( 86 | 'dns-slaves-config-valid', 'dns-backend.available', ), 87 | 'start_designate_services': ( 88 | 'charm.paused', ), 89 | }, 90 | 'when_file_changed': { 91 | 'local_pools_updated': ('/etc/designate/pools.yaml', ), 92 | }, 93 | 'hook': { 94 | 'check_dns_slaves': ('config-changed', ), 95 | }, 96 | } 97 | # test that the hooks were registered via the 98 | # reactive.barbican_handlers 99 | self.registered_hooks_test_helper(handlers, hook_set, []) 100 | 101 | 102 | class TestHandlers(test_utils.PatchHelper): 103 | 104 | def _patch_provide_charm_instance(self): 105 | the_charm = mock.MagicMock() 106 | self.patch_object(handlers.charm, 'provide_charm_instance', 107 | name='provide_charm_instance', 108 | new=mock.MagicMock()) 109 | self.provide_charm_instance().__enter__.return_value = the_charm 110 | self.provide_charm_instance().__exit__.return_value = None 111 | return the_charm 112 | 113 | def test_install_packages(self): 114 | the_charm = self._patch_provide_charm_instance() 115 | self.patch_object(handlers.reactive, 'set_state') 116 | self.patch_object(handlers.reactive, 'remove_state') 117 | handlers.install_packages() 118 | the_charm.install.assert_called_once_with() 119 | calls = [mock.call('shared-db.setup'), 120 | mock.call('base-config.rendered'), 121 | mock.call('db.synched')] 122 | self.remove_state.assert_has_calls(calls) 123 | 124 | def test_setup_amqp_req(self): 125 | self.patch_object(handlers.reactive, 'set_state') 126 | amqp = mock.MagicMock() 127 | handlers.setup_amqp_req(amqp) 128 | amqp.request_access.assert_called_once_with( 129 | username='designate', vhost='openstack') 130 | 131 | def test_database(self): 132 | database = mock.MagicMock() 133 | handlers.setup_database(database) 134 | calls = [ 135 | mock.call( 136 | 'designate', 137 | 'designate', 138 | prefix='designate', 139 | hostname=mock.ANY), 140 | mock.call( 141 | 'dpm', 142 | 'dpm', 143 | prefix='dpm', 144 | hostname=mock.ANY), 145 | ] 146 | database.configure.assert_has_calls(calls) 147 | 148 | def test_setup_endpoint(self): 149 | the_charm = self._patch_provide_charm_instance() 150 | the_charm.service_type = 's1' 151 | the_charm.region = 'r1' 152 | the_charm.public_url = 'p1' 153 | the_charm.internal_url = 'i1' 154 | the_charm.admin_url = 'a1' 155 | args = ['s1', 'r1', 'p1', 'i1', 'a1'] 156 | self.patch_object(handlers, 'is_data_changed', 157 | name='is_data_changed', 158 | new=mock.MagicMock()) 159 | self.is_data_changed().__enter__.return_value = True 160 | self.is_data_changed().__exit__.return_value = None 161 | keystone = mock.MagicMock() 162 | handlers.maybe_setup_endpoint(keystone) 163 | keystone.register_endpoints.assert_called_once_with(*args) 164 | endpoint = mock.MagicMock() 165 | handlers.expose_endpoint(endpoint) 166 | endpoint.expose_endpoint.assert_called_once_with('i1') 167 | endpoint = mock.MagicMock() 168 | self.patch_object(handlers.hookenv, 'config', return_value=False) 169 | handlers.expose_endpoint(endpoint) 170 | endpoint.expose_endpoint.assert_called_once_with('p1') 171 | 172 | def test_configure_designate_basic(self): 173 | the_charm = self._patch_provide_charm_instance() 174 | self.patch_object(handlers.reactive, 'set_state') 175 | self.patch_object(handlers.reactive.RelationBase, 'from_state', 176 | return_value=None) 177 | handlers.configure_designate_basic('arg1', 'arg2') 178 | the_charm.render_base_config.assert_called_once_with( 179 | ('arg1', 'arg2', )) 180 | self.set_state.assert_called_once_with('base-config.rendered') 181 | 182 | def test_run_db_migration(self): 183 | the_charm = self._patch_provide_charm_instance() 184 | self.patch_object(handlers.reactive, 'set_state') 185 | the_charm.db_sync_done.return_value = False 186 | handlers.run_db_migration('arg1', 'arg2') 187 | the_charm.db_sync.assert_called_once_with() 188 | self.assertFalse(self.set_state.called) 189 | the_charm.db_sync.reset_mock() 190 | the_charm.db_sync_done.return_value = True 191 | handlers.run_db_migration('arg1', 'arg2') 192 | the_charm.db_sync.assert_called_once_with() 193 | self.set_state.assert_called_once_with('db.synched') 194 | 195 | def test_sync_pool_manager_cache(self): 196 | the_charm = self._patch_provide_charm_instance() 197 | self.patch_object(handlers.reactive, 'set_state') 198 | the_charm.pool_manager_cache_sync_done.return_value = False 199 | handlers.sync_pool_manager_cache('arg1', 'arg2') 200 | the_charm.pool_manager_cache_sync.assert_called_once_with() 201 | self.assertFalse(self.set_state.called) 202 | the_charm.pool_manager_cache_sync.reset_mock() 203 | the_charm.pool_manager_cache_sync_done.return_value = True 204 | handlers.sync_pool_manager_cache('arg1', 'arg2') 205 | the_charm.pool_manager_cache_sync.assert_called_once_with() 206 | self.set_state.assert_called_once_with('pool-manager-cache.synched') 207 | 208 | def test_configure_designate_full(self): 209 | the_charm = self._patch_provide_charm_instance() 210 | self.patch_object(handlers.reactive.RelationBase, 211 | 'from_state', 212 | return_value=None) 213 | handlers.configure_designate_full('arg1', 'arg2') 214 | the_charm.configure_ssl.assert_called_once_with() 215 | the_charm.render_full_config.assert_called_once_with( 216 | ('arg1', 'arg2', )) 217 | the_charm.create_initial_servers_and_domains.assert_called_once_with() 218 | the_charm.render_with_interfaces.assert_called_once_with( 219 | ('arg1', 'arg2'), configs=mock.ANY) 220 | the_charm.render_rndc_keys.assert_called_once_with() 221 | the_charm.update_pools.assert_called_once_with() 222 | the_charm.upgrade_if_available.assert_called_once_with( 223 | ('arg1', 'arg2', )) 224 | the_charm.remove_obsolete_packages.assert_called_once_with() 225 | 226 | def test_cluster_connected(self): 227 | the_charm = self._patch_provide_charm_instance() 228 | hacluster = mock.MagicMock() 229 | handlers.cluster_connected(hacluster) 230 | the_charm.configure_ha_resources.assert_called_once_with(hacluster) 231 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/templates/mitaka/designate.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | # Where an option is commented out, but filled in this shows the default 3 | # value of that optiona aaa 4 | 5 | ######################## 6 | ## General Configuration 7 | ######################## 8 | # Show more verbose log output (sets INFO log level output) 9 | verbose = {{ options.verbose }} 10 | 11 | # Show debugging output in logs (sets DEBUG log level output) 12 | debug = {{ options.debug }} 13 | 14 | # Top-level directory for maintaining designate's state 15 | #state_path = /var/lib/designate 16 | 17 | # Log Configuration 18 | #log_config = None 19 | 20 | # Log directory 21 | #logdir = /var/log/designate 22 | 23 | # Use "sudo designate-rootwrap /etc/designate/rootwrap.conf" to use the real 24 | # root filter facility. 25 | # Change to "sudo" to skip the filtering and just run the comand directly 26 | #root_helper = sudo designate-rootwrap /etc/designate/rootwrap.conf 27 | 28 | # Which networking API to use, Defaults to neutron 29 | #network_api = neutron 30 | 31 | {%- if options.default_ttl %} 32 | # TTL Value (integer value) 33 | default_ttl = {{ options.default_ttl }} 34 | {%- endif %} 35 | {%- if options.default_soa_refresh_min %} 36 | # SOA refresh-min value (integer value) 37 | default_soa_refresh_min = {{ options.default_soa_refresh_min }} 38 | {%- endif %} 39 | {%- if options.default_soa_refresh_max %} 40 | # SOA max value (integer value) 41 | default_soa_refresh_max = {{ options.default_soa_refresh_max }} 42 | {%- endif %} 43 | {%- if options.default_soa_retry %} 44 | # SOA retry (integer value) 45 | default_soa_retry = {{ options.default_soa_retry }} 46 | {%- endif %} 47 | {%- if options.default_soa_minimum %} 48 | # SOA minimum value (integer value) 49 | default_soa_minimum = {{ options.default_soa_minimum }} 50 | {%- endif %} 51 | # SOA expire (integer value) 52 | default_soa_expire = {{ options.default_soa_expire }} 53 | 54 | #----------------------- 55 | # RabbitMQ Config 56 | #----------------------- 57 | {% include "parts/section-rabbitmq-oslo" %} 58 | 59 | ######################## 60 | ## Service Configuration 61 | ######################## 62 | #----------------------- 63 | # Central Service 64 | #----------------------- 65 | [service:central] 66 | # Number of central worker processes to spawn 67 | workers = {{ options.workers }} 68 | 69 | # Number of central greenthreads to spawn 70 | #threads = 1000 71 | 72 | # Maximum domain name length 73 | #max_domain_name_len = 255 74 | 75 | # Maximum recordset name length 76 | #max_recordset_name_len = 255 77 | 78 | # Minimum TTL 79 | #min_ttl = None 80 | 81 | # The name of the default pool 82 | #default_pool_id = '794ccc2c-d751-44fe-b57f-8894c9f5c842' 83 | 84 | ## Managed resources settings 85 | 86 | # Email to use for managed resources like domains created by the FloatingIP API 87 | managed_resource_email = {{ options.managed_resource_email }} 88 | 89 | {%- if options.managed_resource_tenant_id %} 90 | # Tenant ID to own all managed resources - like auto-created records etc. 91 | managed_resource_tenant_id = {{ options.managed_resource_tenant_id }} 92 | {%- endif %} 93 | 94 | #----------------------- 95 | # API Service 96 | #----------------------- 97 | [service:api] 98 | # Number of api worker processes to spawn 99 | #workers = None 100 | 101 | # Number of api greenthreads to spawn 102 | #threads = 1000 103 | 104 | # Enable host request headers 105 | enable_host_header = true 106 | 107 | # The base uri used in responses 108 | api_base_uri = '{{ options.external_endpoints.designate_api.url }}' 109 | 110 | # Address to bind the API server 111 | # NOTE:tinwood - Listen on every interface; fix for BUG #1734156 112 | api_host = 0.0.0.0 113 | 114 | # Port the bind the API server to 115 | api_port = '{{ options.service_listen_info.designate_api.port }}' 116 | 117 | # Maximum line size of message headers to be accepted. max_header_line may 118 | # need to be increased when using large tokens (typically those generated by 119 | # the Keystone v3 API with big service catalogs). 120 | #max_header_line = 16384 121 | 122 | # Authentication strategy to use - can be either "noauth" or "keystone" 123 | #auth_strategy = keystone 124 | 125 | # Enable Version 1 API (deprecated) 126 | enable_api_v1 = True 127 | 128 | # Enabled API Version 1 extensions 129 | # Can be one or more of : diagnostics, quotas, reports, sync, touch 130 | #enabled_extensions_v1 = 131 | enabled_extensions_v1 = sync, touch 132 | 133 | # Enable Version 2 API 134 | enable_api_v2 = True 135 | 136 | # Enabled API Version 2 extensions 137 | #enabled_extensions_v2 = 138 | 139 | # Default per-page limit for the V2 API, a value of None means show all results 140 | # by default 141 | #default_limit_v2 = 20 142 | 143 | # Max page size in the V2 API 144 | #max_limit_v2 = 1000 145 | 146 | # Enable Admin API (experimental) 147 | #enable_api_admin = False 148 | enable_api_admin = {{ options.enable_admin_api }} 149 | 150 | # Enabled Admin API extensions 151 | # Can be one or more of : reports, quotas, counts, tenants, zones 152 | # zone export is in zones extension 153 | #enabled_extensions_admin = 154 | {% if options.enable_admin_api -%} 155 | enabled_extensions_admin = reports, quotas, counts, tenants, zones 156 | {%- endif %} 157 | 158 | # Default per-page limit for the Admin API, a value of None means show all results 159 | # by default 160 | #default_limit_admin = 20 161 | 162 | # Max page size in the Admin API 163 | #max_limit_admin = 1000 164 | 165 | # Show the pecan HTML based debug interface (v2 only) 166 | # This is only useful for development, and WILL break python-designateclient 167 | # if an error occurs 168 | #pecan_debug = False 169 | 170 | #----------------------- 171 | # Keystone Middleware 172 | #----------------------- 173 | {% include "parts/section-keystone-authtoken" %} 174 | 175 | #----------------------- 176 | # Sink Service 177 | #----------------------- 178 | [service:sink] 179 | # List of notification handlers to enable, configuration of these needs to 180 | # correspond to a [handler:my_driver] section below or else in the config 181 | # Can be one or more of : nova_fixed, neutron_floatingip 182 | enabled_notification_handlers = {{ options.notification_handlers }} 183 | 184 | #----------------------- 185 | # mDNS Service 186 | #----------------------- 187 | [service:mdns] 188 | # Number of mdns worker processes to spawn 189 | #workers = None 190 | 191 | # Number of mdns greenthreads to spawn 192 | #threads = 1000 193 | 194 | # mDNS Bind Host 195 | #host = 0.0.0.0 196 | 197 | # mDNS Port Number 198 | #port = 5354 199 | 200 | # mDNS TCP Backlog 201 | #tcp_backlog = 100 202 | 203 | # mDNS TCP Receive Timeout 204 | #tcp_recv_timeout = 0.5 205 | 206 | # Enforce all incoming queries (including AXFR) are TSIG signed 207 | #query_enforce_tsig = False 208 | 209 | # Send all traffic over TCP 210 | #all_tcp = False 211 | 212 | # Maximum message size to emit 213 | #max_message_size = 65535 214 | 215 | #----------------------- 216 | # Agent Service 217 | #----------------------- 218 | [service:agent] 219 | #workers = None 220 | #host = 0.0.0.0 221 | #port = 5358 222 | #tcp_backlog = 100 223 | #allow_notify = 127.0.0.1 224 | #masters = 127.0.0.1:5354 225 | #backend_driver = fake 226 | #transfer_source = None 227 | #notify_delay = 0 228 | 229 | #----------------------- 230 | # Zone Manager Service 231 | #----------------------- 232 | [service:zone_manager] 233 | # Number of Zone Manager worker processes to spawn 234 | #workers = None 235 | 236 | # Number of Zone Manager greenthreads to spawn 237 | #threads = 1000 238 | 239 | # List of Zone Manager tasks to enable, a value of None will enable all tasks. 240 | # Can be one or more of: periodic_exists 241 | #enabled_tasks = None 242 | 243 | # Whether to allow synchronous zone exports 244 | #export_synchronous = True 245 | 246 | #------------------------ 247 | # Deleted domains purging 248 | #------------------------ 249 | [zone_manager_task:domain_purge] 250 | # How frequently to purge deleted domains, in seconds 251 | #interval = 3600 # 1h 252 | 253 | # How many records to be deleted on each run 254 | #batch_size = 100 255 | 256 | # How old deleted records should be (deleted_at) to be purged, in seconds 257 | time_threshold = {{ options.zone_purge_time_threshold }} 258 | 259 | #----------------------- 260 | # Pool Manager Service 261 | #----------------------- 262 | [service:pool_manager] 263 | # Number of Pool Manager worker processes to spawn 264 | #workers = None 265 | 266 | # Number of Pool Manager greenthreads to spawn 267 | #threads = 1000 268 | 269 | # The ID of the pool managed by this instance of the Pool Manager 270 | pool_id = 794ccc2c-d751-44fe-b57f-8894c9f5c842 271 | 272 | # The percentage of servers requiring a successful update for a domain change 273 | # to be considered active 274 | #threshold_percentage = 100 275 | 276 | # The time to wait for a response from a server 277 | #poll_timeout = 30 278 | 279 | # The time between retrying to send a request and waiting for a response from a 280 | # server 281 | #poll_retry_interval = 15 282 | 283 | # The maximum number of times to retry sending a request and wait for a 284 | # response from a server 285 | #poll_max_retries = 10 286 | 287 | # The time to wait before sending the first request to a server 288 | #poll_delay = 5 289 | 290 | # Enable the recovery thread 291 | #enable_recovery_timer = True 292 | 293 | # The time between recovering from failures 294 | #periodic_recovery_interval = 120 295 | 296 | # Enable the sync thread 297 | #enable_sync_timer = True 298 | 299 | # The time between synchronizing the servers with storage 300 | #periodic_sync_interval = 1800 301 | 302 | # Zones Updated within last N seconds will be syncd. Use None to sync all zones 303 | #periodic_sync_seconds = None 304 | 305 | # The cache driver to use 306 | cache_driver = sqlalchemy 307 | 308 | ################################### 309 | ## Pool Manager Cache Configuration 310 | ################################### 311 | #----------------------- 312 | # SQLAlchemy Pool Manager Cache 313 | #----------------------- 314 | [pool_manager_cache:sqlalchemy] 315 | connection = {{ shared_db.designate_pool_uri }} 316 | #connection = sqlite:///$state_path/designate_pool_manager.sqlite 317 | #connection_debug = 100 318 | #connection_trace = False 319 | #sqlite_synchronous = True 320 | #idle_timeout = 3600 321 | #max_retries = 10 322 | #retry_interval = 10 323 | 324 | #----------------------- 325 | # Memcache Pool Manager Cache 326 | #----------------------- 327 | [pool_manager_cache:memcache] 328 | #memcached_servers = None 329 | #expiration = 3600 330 | 331 | 332 | ############## 333 | ## Network API 334 | ############## 335 | [network_api:neutron] 336 | # Comma separated list of values, formatted "|" 337 | #endpoints = RegionOne|http://localhost:9696 338 | #endpoint_type = publicURL 339 | #timeout = 30 340 | #admin_username = designate 341 | #admin_password = designate 342 | #admin_tenant_name = designate 343 | #auth_url = http://localhost:35357/v2.0 344 | #insecure = False 345 | #auth_strategy = keystone 346 | #ca_certificates_file = 347 | 348 | ######################## 349 | ## Storage Configuration 350 | ######################## 351 | #----------------------- 352 | # SQLAlchemy Storage 353 | #----------------------- 354 | [storage:sqlalchemy] 355 | # Database connection string - to configure options for a given implementation 356 | # like sqlalchemy or other see below 357 | #connection = sqlite:///$state_path/designate.sqlite 358 | connection = {{ shared_db.designate_uri }} 359 | #connection_debug = 0 360 | #connection_trace = False 361 | #sqlite_synchronous = True 362 | #idle_timeout = 3600 363 | #max_retries = 10 364 | #retry_interval = 10 365 | 366 | ######################## 367 | ## Handler Configuration 368 | ######################## 369 | #----------------------- 370 | # Nova Fixed Handler 371 | #----------------------- 372 | #format = '%(hostname)s.%(domain)s' 373 | 374 | ############################# 375 | ## Agent Backend Configuration 376 | ############################# 377 | [backend:agent:bind9] 378 | #rndc_config_file = /etc/rndc.conf 379 | #rndc_key_file = /etc/rndc.key 380 | #zone_file_path = $state_path/zones 381 | #query_destination = 127.0.0.1 382 | # 383 | [backend:agent:denominator] 384 | #name = dynect 385 | #config_file = /etc/denominator.conf 386 | 387 | ######################## 388 | ## Library Configuration 389 | ######################## 390 | [oslo_concurrency] 391 | # Path for Oslo Concurrency to store lock files, defaults to the value 392 | # of the state_path setting. 393 | #lock_path = $state_path 394 | 395 | ######################## 396 | ## Coordination 397 | ######################## 398 | [coordination] 399 | {% if coordinator_memcached.url -%} 400 | backend_url = {{ coordinator_memcached.url }} 401 | {%- endif %} 402 | 403 | ######################## 404 | ## Hook Points 405 | ######################## 406 | # Hook Points are enabled when added to the config and there has been 407 | # a package that provides the corresponding named designate.hook_point 408 | # entry point. 409 | 410 | # [hook_point:name_of_hook_point] 411 | # some_param_for_hook = 42 412 | # Hooks can be disabled in the config 413 | # enabled = False 414 | 415 | # Hook can also be applied to the import path when the hook has not 416 | # been given an explicit name. The name is created from the hook 417 | # target function / method: 418 | # 419 | # name = '%s.%s' % (func.__module__, func.__name__) 420 | 421 | # [hook_point:designate.api.v2.controllers.zones.get_one] 422 | 423 | {% include "parts/section-oslo-middleware" %} 424 | -------------------------------------------------------------------------------- /src/templates/queens/designate.conf: -------------------------------------------------------------------------------- 1 | # Queens 2 | [DEFAULT] 3 | # Where an option is commented out, but filled in this shows the default 4 | # value of that optiona aaa 5 | 6 | ######################## 7 | ## General Configuration 8 | ######################## 9 | # Show more verbose log output (sets INFO log level output) 10 | verbose = {{ options.verbose }} 11 | 12 | # Show debugging output in logs (sets DEBUG log level output) 13 | debug = {{ options.debug }} 14 | 15 | # Top-level directory for maintaining designate's state 16 | #state_path = /var/lib/designate 17 | 18 | # Log Configuration 19 | #log_config = None 20 | 21 | # Log directory 22 | #logdir = /var/log/designate 23 | 24 | # Use "sudo designate-rootwrap /etc/designate/rootwrap.conf" to use the real 25 | # root filter facility. 26 | # Change to "sudo" to skip the filtering and just run the comand directly 27 | #root_helper = sudo designate-rootwrap /etc/designate/rootwrap.conf 28 | 29 | # Which networking API to use, Defaults to neutron 30 | #network_api = neutron 31 | 32 | {%- if options.default_ttl %} 33 | # TTL Value (integer value) 34 | default_ttl = {{ options.default_ttl }} 35 | {%- endif %} 36 | {%- if options.default_soa_refresh_min %} 37 | # SOA refresh-min value (integer value) 38 | default_soa_refresh_min = {{ options.default_soa_refresh_min }} 39 | {%- endif %} 40 | {%- if options.default_soa_refresh_max %} 41 | # SOA max value (integer value) 42 | default_soa_refresh_max = {{ options.default_soa_refresh_max }} 43 | {%- endif %} 44 | {%- if options.default_soa_retry %} 45 | # SOA retry (integer value) 46 | default_soa_retry = {{ options.default_soa_retry }} 47 | {%- endif %} 48 | {%- if options.default_soa_minimum %} 49 | # SOA minimum value (integer value) 50 | default_soa_minimum = {{ options.default_soa_minimum }} 51 | {%- endif %} 52 | # SOA expire (integer value) 53 | default_soa_expire = {{ options.default_soa_expire }} 54 | 55 | {% include "parts/section-transport-url" %} 56 | 57 | #----------------------- 58 | # RabbitMQ Config 59 | #----------------------- 60 | {% include "parts/section-oslo-messaging-rabbit" %} 61 | 62 | ######################## 63 | ## Service Configuration 64 | ######################## 65 | #----------------------- 66 | # Central Service 67 | #----------------------- 68 | [service:central] 69 | # Number of central worker processes to spawn 70 | workers = {{ options.workers }} 71 | 72 | # Number of central greenthreads to spawn 73 | #threads = 1000 74 | 75 | # Maximum domain name length 76 | #max_domain_name_len = 255 77 | 78 | # Maximum recordset name length 79 | #max_recordset_name_len = 255 80 | 81 | # Minimum TTL 82 | #min_ttl = None 83 | 84 | # The name of the default pool 85 | #default_pool_id = '794ccc2c-d751-44fe-b57f-8894c9f5c842' 86 | 87 | ## Managed resources settings 88 | 89 | # Email to use for managed resources like domains created by the FloatingIP API 90 | managed_resource_email = {{ options.managed_resource_email }} 91 | 92 | {%- if options.managed_resource_tenant_id %} 93 | # Tenant ID to own all managed resources - like auto-created records etc. 94 | managed_resource_tenant_id = {{ options.managed_resource_tenant_id }} 95 | {%- endif %} 96 | 97 | #----------------------- 98 | # API Service 99 | #----------------------- 100 | [service:api] 101 | # Number of api worker processes to spawn 102 | #workers = None 103 | 104 | # Number of api greenthreads to spawn 105 | #threads = 1000 106 | 107 | # Enable host request headers 108 | enable_host_header = true 109 | 110 | # The base uri used in responses 111 | api_base_uri = '{{ options.external_endpoints.designate_api.url }}' 112 | 113 | # Address to bind the API server 114 | # NOTE:tinwood - Listen on every interface; fix for BUG #1734156 115 | api_host = 0.0.0.0 116 | 117 | # Port the bind the API server to 118 | api_port = '{{ options.service_listen_info.designate_api.port }}' 119 | 120 | # Maximum line size of message headers to be accepted. max_header_line may 121 | # need to be increased when using large tokens (typically those generated by 122 | # the Keystone v3 API with big service catalogs). 123 | #max_header_line = 16384 124 | 125 | # Authentication strategy to use - can be either "noauth" or "keystone" 126 | #auth_strategy = keystone 127 | 128 | # Enable Version 1 API (deprecated) 129 | enable_api_v1 = True 130 | 131 | # Enabled API Version 1 extensions 132 | # Can be one or more of : diagnostics, quotas, reports, sync, touch 133 | #enabled_extensions_v1 = 134 | enabled_extensions_v1 = sync, touch 135 | 136 | # Enable Version 2 API 137 | enable_api_v2 = True 138 | 139 | # Enabled API Version 2 extensions 140 | #enabled_extensions_v2 = 141 | 142 | # Default per-page limit for the V2 API, a value of None means show all results 143 | # by default 144 | #default_limit_v2 = 20 145 | 146 | # Max page size in the V2 API 147 | #max_limit_v2 = 1000 148 | 149 | # Enable Admin API (experimental) 150 | #enable_api_admin = False 151 | enable_api_admin = {{ options.enable_admin_api }} 152 | 153 | # Enabled Admin API extensions 154 | # Can be one or more of : reports, quotas, counts, tenants, zones 155 | # zone export is in zones extension 156 | #enabled_extensions_admin = 157 | {% if options.enable_admin_api -%} 158 | enabled_extensions_admin = reports, quotas, counts, tenants, zones 159 | {%- endif %} 160 | 161 | # Default per-page limit for the Admin API, a value of None means show all results 162 | # by default 163 | #default_limit_admin = 20 164 | 165 | # Max page size in the Admin API 166 | #max_limit_admin = 1000 167 | 168 | # Show the pecan HTML based debug interface (v2 only) 169 | # This is only useful for development, and WILL break python-designateclient 170 | # if an error occurs 171 | #pecan_debug = False 172 | 173 | #----------------------- 174 | # Keystone Middleware 175 | #----------------------- 176 | {% include "parts/section-keystone-authtoken" %} 177 | 178 | #----------------------- 179 | # Sink Service 180 | #----------------------- 181 | [service:sink] 182 | # List of notification handlers to enable, configuration of these needs to 183 | # correspond to a [handler:my_driver] section below or else in the config 184 | # Can be one or more of : nova_fixed, neutron_floatingip 185 | enabled_notification_handlers = {{ options.notification_handlers }} 186 | 187 | #----------------------- 188 | # mDNS Service 189 | #----------------------- 190 | [service:mdns] 191 | # Number of mdns worker processes to spawn 192 | #workers = None 193 | 194 | # Number of mdns greenthreads to spawn 195 | #threads = 1000 196 | 197 | # mDNS Bind Host 198 | #host = 0.0.0.0 199 | 200 | # mDNS Port Number 201 | #port = 5354 202 | 203 | # mDNS TCP Backlog 204 | #tcp_backlog = 100 205 | 206 | # mDNS TCP Receive Timeout 207 | #tcp_recv_timeout = 0.5 208 | 209 | # Enforce all incoming queries (including AXFR) are TSIG signed 210 | #query_enforce_tsig = False 211 | 212 | # Send all traffic over TCP 213 | #all_tcp = False 214 | 215 | # Maximum message size to emit 216 | #max_message_size = 65535 217 | 218 | #----------------------- 219 | # Agent Service 220 | #----------------------- 221 | [service:agent] 222 | #workers = None 223 | #host = 0.0.0.0 224 | #port = 5358 225 | #tcp_backlog = 100 226 | #allow_notify = 127.0.0.1 227 | #masters = 127.0.0.1:5354 228 | #backend_driver = fake 229 | #transfer_source = None 230 | #notify_delay = 0 231 | 232 | #----------------------- 233 | # Zone Manager Service 234 | #----------------------- 235 | [service:zone_manager] 236 | # Number of Zone Manager worker processes to spawn 237 | #workers = None 238 | 239 | # Number of Zone Manager greenthreads to spawn 240 | #threads = 1000 241 | 242 | # List of Zone Manager tasks to enable, a value of None will enable all tasks. 243 | # Can be one or more of: periodic_exists 244 | #enabled_tasks = None 245 | 246 | # Whether to allow synchronous zone exports 247 | #export_synchronous = True 248 | 249 | #------------------------ 250 | # Deleted domains purging 251 | #------------------------ 252 | [zone_manager_task:domain_purge] 253 | # How frequently to purge deleted domains, in seconds 254 | #interval = 3600 # 1h 255 | 256 | # How many records to be deleted on each run 257 | #batch_size = 100 258 | 259 | # How old deleted records should be (deleted_at) to be purged, in seconds 260 | time_threshold = {{ options.zone_purge_time_threshold }} 261 | 262 | #----------------------- 263 | # Pool Manager Service 264 | #----------------------- 265 | [service:pool_manager] 266 | # Number of Pool Manager worker processes to spawn 267 | #workers = None 268 | 269 | # Number of Pool Manager greenthreads to spawn 270 | #threads = 1000 271 | 272 | # The ID of the pool managed by this instance of the Pool Manager 273 | pool_id = 794ccc2c-d751-44fe-b57f-8894c9f5c842 274 | 275 | # The percentage of servers requiring a successful update for a domain change 276 | # to be considered active 277 | #threshold_percentage = 100 278 | 279 | # The time to wait for a response from a server 280 | #poll_timeout = 30 281 | 282 | # The time between retrying to send a request and waiting for a response from a 283 | # server 284 | #poll_retry_interval = 15 285 | 286 | # The maximum number of times to retry sending a request and wait for a 287 | # response from a server 288 | #poll_max_retries = 10 289 | 290 | # The time to wait before sending the first request to a server 291 | #poll_delay = 5 292 | 293 | # Enable the recovery thread 294 | #enable_recovery_timer = True 295 | 296 | # The time between recovering from failures 297 | #periodic_recovery_interval = 120 298 | 299 | # Enable the sync thread 300 | #enable_sync_timer = True 301 | 302 | # The time between synchronizing the servers with storage 303 | #periodic_sync_interval = 1800 304 | 305 | # Zones Updated within last N seconds will be syncd. Use None to sync all zones 306 | #periodic_sync_seconds = None 307 | 308 | # The cache driver to use 309 | cache_driver = sqlalchemy 310 | 311 | ################################### 312 | ## Pool Manager Cache Configuration 313 | ################################### 314 | #----------------------- 315 | # SQLAlchemy Pool Manager Cache 316 | #----------------------- 317 | [pool_manager_cache:sqlalchemy] 318 | connection = {{ shared_db.designate_pool_uri }} 319 | #connection = sqlite:///$state_path/designate_pool_manager.sqlite 320 | #connection_debug = 100 321 | #connection_trace = False 322 | #sqlite_synchronous = True 323 | #idle_timeout = 3600 324 | #max_retries = 10 325 | #retry_interval = 10 326 | 327 | #----------------------- 328 | # Memcache Pool Manager Cache 329 | #----------------------- 330 | [pool_manager_cache:memcache] 331 | #memcached_servers = None 332 | #expiration = 3600 333 | 334 | 335 | ############## 336 | ## Network API 337 | ############## 338 | [network_api:neutron] 339 | # Comma separated list of values, formatted "|" 340 | #endpoints = RegionOne|http://localhost:9696 341 | #endpoint_type = publicURL 342 | #timeout = 30 343 | #admin_username = designate 344 | #admin_password = designate 345 | #admin_tenant_name = designate 346 | #auth_url = http://localhost:35357/v2.0 347 | #insecure = False 348 | #auth_strategy = keystone 349 | #ca_certificates_file = 350 | 351 | ######################## 352 | ## Storage Configuration 353 | ######################## 354 | #----------------------- 355 | # SQLAlchemy Storage 356 | #----------------------- 357 | [storage:sqlalchemy] 358 | # Database connection string - to configure options for a given implementation 359 | # like sqlalchemy or other see below 360 | #connection = sqlite:///$state_path/designate.sqlite 361 | connection = {{ shared_db.designate_uri }} 362 | #connection_debug = 0 363 | #connection_trace = False 364 | #sqlite_synchronous = True 365 | #idle_timeout = 3600 366 | #max_retries = 10 367 | #retry_interval = 10 368 | 369 | ######################## 370 | ## Handler Configuration 371 | ######################## 372 | #----------------------- 373 | # Nova Fixed Handler 374 | #----------------------- 375 | #format = '%(hostname)s.%(domain)s' 376 | 377 | ############################# 378 | ## Agent Backend Configuration 379 | ############################# 380 | [backend:agent:bind9] 381 | #rndc_config_file = /etc/rndc.conf 382 | #rndc_key_file = /etc/rndc.key 383 | #zone_file_path = $state_path/zones 384 | #query_destination = 127.0.0.1 385 | # 386 | [backend:agent:denominator] 387 | #name = dynect 388 | #config_file = /etc/denominator.conf 389 | 390 | ######################## 391 | ## Library Configuration 392 | ######################## 393 | [oslo_concurrency] 394 | # Path for Oslo Concurrency to store lock files, defaults to the value 395 | # of the state_path setting. 396 | #lock_path = $state_path 397 | 398 | ######################## 399 | ## Coordination 400 | ######################## 401 | [coordination] 402 | {% if coordinator_memcached.url -%} 403 | backend_url = {{ coordinator_memcached.url }} 404 | {%- endif %} 405 | 406 | ######################## 407 | ## Hook Points 408 | ######################## 409 | # Hook Points are enabled when added to the config and there has been 410 | # a package that provides the corresponding named designate.hook_point 411 | # entry point. 412 | 413 | # [hook_point:name_of_hook_point] 414 | # some_param_for_hook = 42 415 | # Hooks can be disabled in the config 416 | # enabled = False 417 | 418 | # Hook can also be applied to the import path when the hook has not 419 | # been given an explicit name. The name is created from the hook 420 | # target function / method: 421 | # 422 | # name = '%s.%s' % (func.__module__, func.__name__) 423 | 424 | # [hook_point:designate.api.v2.controllers.zones.get_one] 425 | 426 | {% include "parts/section-oslo-middleware" %} 427 | -------------------------------------------------------------------------------- /src/reactive/designate_handlers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import subprocess 16 | 17 | import charm.openstack.designate as designate 18 | import charms.reactive as reactive 19 | import charms.reactive.relations as relations 20 | import charmhelpers.core.hookenv as hookenv 21 | import charmhelpers.core.host as host 22 | import charmhelpers.contrib.network.ip as ip 23 | 24 | import charms_openstack.charm as charm 25 | from charms_openstack.charm.utils import is_data_changed 26 | 27 | 28 | charm.use_defaults( 29 | 'certificates.available', 30 | 'cluster.available', 31 | ) 32 | 33 | # If either dns-backend.available is set OR config('dns-slaves') is valid, then 34 | # the following state will be set. 35 | DNS_CONFIG_AVAILABLE = 'dns-config.available' 36 | 37 | COMPLETE_INTERFACE_STATES = [ 38 | DNS_CONFIG_AVAILABLE, 39 | 'shared-db.available', 40 | 'identity-service.available', 41 | 'amqp.available', 42 | 'coordinator-memcached.available', 43 | ] 44 | 45 | 46 | @reactive.hook('config-changed') 47 | def check_dns_slaves(): 48 | """verify if the config('dns-slaves') is valid and set or remove the state 49 | accordingly. Note, that hooks run BEFORE the reactive handlers so this 50 | should happen first during a hook. 51 | """ 52 | with charm.provide_charm_instance() as instance: 53 | # ensure policy.d overrides are picked up 54 | instance.config_changed() 55 | if hookenv.config('dns-slaves'): 56 | if not instance.options.invalid_pool_config(): 57 | reactive.set_state('dns-slaves-config-valid') 58 | return 59 | reactive.remove_state('dns-slaves-config-valid') 60 | 61 | 62 | @reactive.when_not('is-update-status-hook') 63 | @reactive.when_any('dns-slaves-config-valid', 64 | 'dns-backend.available') 65 | def set_dns_config_available(*args): 66 | reactive.set_state(DNS_CONFIG_AVAILABLE) 67 | 68 | 69 | @reactive.when_none('dns-slaves-config-valid', 70 | 'dns-backend.available') 71 | def clear_dns_config_available(): 72 | reactive.remove_state(DNS_CONFIG_AVAILABLE) 73 | 74 | 75 | @reactive.when_not('installed') 76 | def install_packages(): 77 | """Install charms packages""" 78 | with charm.provide_charm_instance() as instance: 79 | instance.install() 80 | reactive.set_state('installed') 81 | reactive.remove_state('shared-db.setup') 82 | reactive.remove_state('base-config.rendered') 83 | reactive.remove_state('db.synched') 84 | reactive.remove_state('pool-manager-cache.synched') 85 | 86 | 87 | @reactive.when_not('is-update-status-hook') 88 | @reactive.when('amqp.connected') 89 | def setup_amqp_req(amqp): 90 | """Send request for rabbit access and vhost""" 91 | amqp.request_access(username='designate', 92 | vhost='openstack') 93 | 94 | 95 | @reactive.when('base-config.rendered') 96 | @reactive.when_not('config.rendered') 97 | def config_rendered(): 98 | """Set the config.rendered state when ready for operation. 99 | 100 | The config.rendered flag is used by the default handlers in 101 | charms.openstack to enable/disable services based on the 102 | readiness of the deployment. This functionality ensure 103 | that the Designate services start up only after the 104 | database has been synced. 105 | LP#1925233 106 | """ 107 | reactive.set_state('config.rendered') 108 | 109 | 110 | @reactive.when_not('is-update-status-hook') 111 | @reactive.when_none('charm.paused') 112 | @reactive.when('config.rendered', 'base-config.rendered') 113 | def start_designate_services(): 114 | """Enable services when database is synchronized""" 115 | with charm.provide_charm_instance() as instance: 116 | if instance.db_sync_done(): 117 | instance.enable_services() 118 | else: 119 | hookenv.log("Services not enabled, waiting for db sync", 120 | level=hookenv.WARNING) 121 | 122 | 123 | @reactive.when('shared-db.connected') 124 | @reactive.when_not('shared-db.setup') 125 | def setup_database(database): 126 | """Send request designate accounts and dbs""" 127 | hostname = None 128 | if database.access_network(): 129 | hostname = ip.get_address_in_network(database.access_network()) 130 | database.configure('designate', 'designate', prefix='designate', 131 | hostname=hostname) 132 | database.configure('dpm', 'dpm', prefix='dpm', 133 | hostname=hostname) 134 | if database.base_data_complete(): 135 | reactive.set_state('shared-db.setup') 136 | 137 | 138 | @reactive.when_not('is-update-status-hook') 139 | @reactive.when('identity-service.connected') 140 | def maybe_setup_endpoint(keystone): 141 | """When the keystone interface connects, register this unit in the keystone 142 | catalogue. 143 | """ 144 | with charm.provide_charm_instance() as instance: 145 | args = [instance.service_type, instance.region, instance.public_url, 146 | instance.internal_url, instance.admin_url] 147 | # This function checkes that the data has changed before sending it 148 | with is_data_changed('charms.openstack.register-endpoints', args) as c: 149 | if c: 150 | keystone.register_endpoints(*args) 151 | 152 | 153 | @reactive.when_not('is-update-status-hook') 154 | @reactive.when('cluster.connected') 155 | def expose_rndc_address(cluster): 156 | rndc_address = ip.get_relation_ip('dns-backend') 157 | cluster.set_address('rndc', rndc_address) 158 | 159 | 160 | @reactive.when_not('base-config.rendered') 161 | @reactive.when(*COMPLETE_INTERFACE_STATES) 162 | def configure_designate_basic(*args): 163 | """Configure the minimum to bootstrap designate""" 164 | # If cluster relation is available it needs to passed in 165 | cluster = relations.endpoint_from_flag('cluster.available') 166 | if cluster is not None: 167 | args = args + (cluster, ) 168 | dns_backend = relations.endpoint_from_flag('dns-backend.available') 169 | if dns_backend is not None: 170 | args = args + (dns_backend, ) 171 | with charm.provide_charm_instance() as instance: 172 | instance.render_base_config(args) 173 | instance.disable_services() 174 | reactive.set_state('base-config.rendered') 175 | 176 | 177 | @reactive.when_not('db.synched') 178 | @reactive.when('base-config.rendered') 179 | @reactive.when(*COMPLETE_INTERFACE_STATES) 180 | def run_db_migration(*args): 181 | """Run database migrations""" 182 | with charm.provide_charm_instance() as instance: 183 | instance.db_sync() 184 | if instance.db_sync_done(): 185 | reactive.set_state('db.synched') 186 | 187 | 188 | @reactive.when_not('pool-manager-cache.synched') 189 | @reactive.when('base-config.rendered') 190 | @reactive.when(*COMPLETE_INTERFACE_STATES) 191 | def sync_pool_manager_cache(*args): 192 | with charm.provide_charm_instance() as instance: 193 | instance.pool_manager_cache_sync() 194 | if instance.pool_manager_cache_sync_done(): 195 | reactive.set_state('pool-manager-cache.synched') 196 | 197 | 198 | @reactive.when_not('is-update-status-hook') 199 | @reactive.when('db.synched') 200 | @reactive.when('pool-manager-cache.synched') 201 | @reactive.when(*COMPLETE_INTERFACE_STATES) 202 | def configure_designate_full(*args): 203 | """Write out all designate config include bootstrap domain info""" 204 | # If cluster relation is available it needs to passed in 205 | cluster = relations.endpoint_from_flag('cluster.available') 206 | if cluster is not None: 207 | args = args + (cluster, ) 208 | dns_backend = relations.endpoint_from_flag('dns-backend.available') 209 | if dns_backend is not None: 210 | args = args + (dns_backend, ) 211 | with charm.provide_charm_instance() as instance: 212 | instance.upgrade_if_available(args) 213 | # Workaround to purge packages in case of upgrades from one release to 214 | # another 215 | # Eventhough upgrade_if_available() purges the unnecessary packages, 216 | # reactive/layer_openstack.py::default_upgrade_charm again tries to 217 | # install due to singleton class pointing to older release charm 218 | # instance i.e., it installs all the packages mentioned in older 219 | # release. After that even though configure_designate_full() is 220 | # triggered, upgrade_if_available does nothing as the installed 221 | # packages are already of same release as expected and it 222 | # did not care about purge packages. 223 | # Example scenario that triggered this change is removal of 224 | # designate-agent from caracal release 225 | instance.remove_obsolete_packages() 226 | instance.configure_ssl() 227 | instance.render_full_config(args) 228 | try: 229 | # the following function should only run once for the leader. 230 | if instance.configure_sink(): 231 | instance.create_initial_servers_and_domains() 232 | _render_sink_configs(instance, args) 233 | instance.render_rndc_keys() 234 | instance.update_pools() 235 | except subprocess.CalledProcessError as e: 236 | hookenv.log("ensure_api_responding() errored out: {}" 237 | .format(str(e)), 238 | level=hookenv.ERROR) 239 | 240 | 241 | @reactive.when_not('is-update-status-hook') 242 | @reactive.when('dns-backend.available') 243 | @reactive.when('db.synched') 244 | @reactive.when(*COMPLETE_INTERFACE_STATES) 245 | def configure_dns_backend_rndc_keys(*args): 246 | """Write the dns-backend relation configuration files and restart 247 | designate-worker to apply the new config. 248 | """ 249 | with charm.provide_charm_instance() as instance: 250 | instance.render_relation_rndc_keys() 251 | host.service_restart('designate-worker') 252 | 253 | 254 | def _render_sink_configs(instance, interfaces_list): 255 | """Helper: use the singleton from the DesignateCharm to render sink configs 256 | 257 | @param instance: an instance that has the render_with_intefaces() method 258 | @param interfaces_list: List of instances of interface classes. 259 | @returns: None 260 | """ 261 | configs = [designate.NOVA_SINK_FILE, 262 | designate.NEUTRON_SINK_FILE, 263 | designate.DESIGNATE_DEFAULT] 264 | instance.render_with_interfaces(interfaces_list, configs=configs) 265 | 266 | 267 | @reactive.when_not('is-update-status-hook') 268 | @reactive.when('ha.connected') 269 | def cluster_connected(hacluster): 270 | """Configure HA resources in corosync""" 271 | with charm.provide_charm_instance() as instance: 272 | instance.configure_ha_resources(hacluster) 273 | 274 | 275 | @reactive.when_not('is-update-status-hook') 276 | @reactive.when('dnsaas.connected') 277 | def expose_endpoint(endpoint): 278 | with charm.provide_charm_instance() as instance: 279 | if hookenv.config('use-internal-endpoints'): 280 | endpoint.expose_endpoint(instance.internal_url) 281 | else: 282 | endpoint.expose_endpoint(instance.public_url) 283 | 284 | 285 | @reactive.when_not('dont-set-assess-status') 286 | def run_assess_status_on_every_hook(): 287 | """The call to charm instance.assess_status() sets up the assess status 288 | functionality to be called atexit() of the charm. i.e. as the last thing 289 | it does. Thus, this handle gets called for EVERY hook invocation, which 290 | means that no other handlers need to call the assess_status function. 291 | """ 292 | with charm.provide_charm_instance() as instance: 293 | instance.assess_status() 294 | 295 | 296 | @reactive.when('leadership.changed.pool-yaml-hash') 297 | def remote_pools_updated(): 298 | hookenv.log( 299 | "Pools updated on remote host, restarting pool manager", 300 | level=hookenv.DEBUG) 301 | host.service_restart('designate-pool-manager') 302 | 303 | 304 | @reactive.when_file_changed(designate.POOLS_YAML) 305 | def local_pools_updated(): 306 | hookenv.log( 307 | "Pools updated locally, restarting pool manager", 308 | level=hookenv.DEBUG) 309 | host.service_restart('designate-pool-manager') 310 | 311 | 312 | @reactive.when('shared-db.setup') 313 | @reactive.when_not('shared-db.connected') 314 | def reset_shared_db(): 315 | """Clear flags on shared-db departed. 316 | 317 | When shared-db is rejoined the charm will reconfigure the DB IFF these 318 | flags have been cleared. See LP Bug#1887265 319 | """ 320 | reactive.remove_state('shared-db.setup') 321 | reactive.remove_state('db.synched') 322 | 323 | 324 | @reactive.when_not('is-update-status-hook') 325 | @reactive.when('base-config.rendered') 326 | @reactive.when_any('config.changed.nagios_context', 327 | 'config.changed.nagios_servicegroups', 328 | 'config.changed.nameservers', 329 | 'config.changed.nrpe-nameserver-check-host', 330 | 'endpoint.nrpe-external-master.changed', 331 | 'nrpe-external-master.available') 332 | def configure_nrpe(): 333 | """Handle config-changed for NRPE options.""" 334 | with charm.provide_charm_instance() as charm_instance: 335 | charm_instance.render_nrpe() 336 | # regenerate service checks for upstream nameservers 337 | charm_instance.remove_nrpe_nameserver_checks() 338 | charm_instance.add_nrpe_nameserver_checks() 339 | -------------------------------------------------------------------------------- /unit_tests/test_lib_charm_openstack_designate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import contextlib 16 | import unittest 17 | 18 | from unittest import mock 19 | 20 | import charm.openstack.designate as designate 21 | 22 | 23 | def FakeConfig(init_dict): 24 | 25 | def _config(key=None): 26 | return init_dict[key] if key else init_dict 27 | 28 | return _config 29 | 30 | 31 | class Helper(unittest.TestCase): 32 | 33 | def setUp(self): 34 | self._patches = {} 35 | self._patches_start = {} 36 | self.ch_config_patch = mock.patch('charmhelpers.core.hookenv.config') 37 | self.ch_config = self.ch_config_patch.start() 38 | self.ch_config.side_effect = lambda: {'ssl_param': None} 39 | 40 | def tearDown(self): 41 | for k, v in self._patches.items(): 42 | v.stop() 43 | setattr(self, k, None) 44 | self._patches = None 45 | self._patches_start = None 46 | self.ch_config_patch.stop() 47 | 48 | def patch(self, obj, attr, return_value=None, **kwargs): 49 | mocked = mock.patch.object(obj, attr, **kwargs) 50 | self._patches[attr] = mocked 51 | started = mocked.start() 52 | started.return_value = return_value 53 | self._patches_start[attr] = started 54 | setattr(self, attr, started) 55 | 56 | def patch_object(self, obj, attr, return_value=None, name=None, new=None): 57 | if name is None: 58 | name = attr 59 | if new is not None: 60 | mocked = mock.patch.object(obj, attr, new=new) 61 | else: 62 | mocked = mock.patch.object(obj, attr) 63 | self._patches[name] = mocked 64 | started = mocked.start() 65 | if new is None: 66 | started.return_value = return_value 67 | self._patches_start[name] = started 68 | setattr(self, name, started) 69 | 70 | 71 | class TestDesignateDBAdapter(Helper): 72 | 73 | def fake_get_uri(self, prefix): 74 | return 'mysql://uri/{}-database'.format(prefix) 75 | 76 | def test_designate_uri(self): 77 | relation = mock.MagicMock() 78 | a = designate.DesignateDBAdapter(relation) 79 | self.patch(designate.DesignateDBAdapter, 'get_uri') 80 | self.get_uri.side_effect = self.fake_get_uri 81 | self.assertEqual(a.designate_uri, 'mysql://uri/designate-database') 82 | self.assertEqual(a.designate_pool_uri, 'mysql://uri/dpm-database') 83 | 84 | 85 | class TestBindRNDCRelationAdapter(Helper): 86 | 87 | def test_slave_ips(self): 88 | relation = mock.MagicMock() 89 | relation.slave_ips.return_value = 'slave_ips_info' 90 | a = designate.BindRNDCRelationAdapter(relation) 91 | self.assertEqual(a.slave_ips, 'slave_ips_info') 92 | 93 | def test_pool_configs(self): 94 | relation = mock.MagicMock() 95 | _slave_ips = [ 96 | {'unit': 'unit/1', 97 | 'address': 'addr1'}, 98 | {'unit': 'unit/2', 99 | 'address': 'addr2'}] 100 | with mock.patch.object(designate.BindRNDCRelationAdapter, 101 | 'slave_ips', new=_slave_ips): 102 | a = designate.BindRNDCRelationAdapter(relation) 103 | expect = [{'address': 'addr1', 104 | 'nameserver': 'nameserver_unit', 105 | 'pool_target': 'nameserver_unit', 106 | 'rndc_key_file': '/etc/designate/rndc_unit.key'}, 107 | {'address': 'addr2', 108 | 'nameserver': 'nameserver_unit', 109 | 'pool_target': 'nameserver_unit', 110 | 'rndc_key_file': '/etc/designate/rndc_unit.key'}] 111 | self.assertEqual(a.pool_config, expect) 112 | self.assertEqual( 113 | a.pool_targets, 114 | 'nameserver_unit, nameserver_unit') 115 | self.assertEqual(a.slave_addresses, 'addr1:53, addr2:53') 116 | 117 | def test_rndc_info(self): 118 | relation = mock.MagicMock() 119 | relation.rndc_info = 'rndcstuff' 120 | a = designate.BindRNDCRelationAdapter(relation) 121 | self.assertEqual(a.rndc_info, 'rndcstuff') 122 | 123 | 124 | class TestDesignateConfigurationAdapter(Helper): 125 | 126 | def test_designate_configuration_adapter_pool_info(self): 127 | relation = mock.MagicMock() 128 | self.patch( 129 | designate.openstack_adapters.APIConfigurationAdapter, 130 | 'get_network_addresses') 131 | test_config = { 132 | 'dns_slaves': 'ip1:port1:key1 ip2:port2:key2', 133 | } 134 | with mock.patch.object(designate.openstack_adapters.hookenv, 'config', 135 | new=lambda: test_config): 136 | a = designate.DesignateConfigurationAdapter(relation) 137 | expect = [{'address': 'ip1', 138 | 'nameserver': 'nameserver_ip1', 139 | 'pool_target': 'nameserver_ip1', 140 | 'rndc_key_file': '/etc/designate/rndc_ip1.key'}, 141 | {'address': 'ip2', 142 | 'nameserver': 'nameserver_ip2', 143 | 'pool_target': 'nameserver_ip2', 144 | 'rndc_key_file': '/etc/designate/rndc_ip2.key'}] 145 | self.assertEqual(a.pool_config, expect) 146 | self.assertEqual(a.pool_targets, 'nameserver_ip1, nameserver_ip2') 147 | self.assertEqual(a.slave_addresses, 'ip1:53, ip2:53') 148 | 149 | def test_designate_configuration_domains(self): 150 | relation = mock.MagicMock() 151 | self.patch( 152 | designate.openstack_adapters.APIConfigurationAdapter, 153 | 'get_network_addresses') 154 | test_config = { 155 | 'nova-domain': 'bob.com', 156 | 'neutron-domain': 'bill.com', 157 | } 158 | domain_map = { 159 | 'bob.com': 12, 160 | 'bill.com': 13, 161 | } 162 | with mock.patch.object(designate.hookenv, 'config', 163 | side_effect=FakeConfig(test_config)): 164 | self.patch(designate.DesignateCharm, 'get_domain_id') 165 | self.get_domain_id.side_effect = lambda x: domain_map.get(x) 166 | a = designate.DesignateConfigurationAdapter(relation) 167 | self.assertEqual(a.nova_domain_id, 12) 168 | self.assertEqual(a.neutron_domain_id, 13) 169 | 170 | def test_designate_configuration_daemon_args(self): 171 | relation = mock.MagicMock() 172 | self.patch( 173 | designate.openstack_adapters.APIConfigurationAdapter, 174 | 'get_network_addresses') 175 | self.patch(designate.os.path, 'exists', return_value=True) 176 | a = designate.DesignateConfigurationAdapter(relation) 177 | self.assertEqual( 178 | a.nova_conf_args, 179 | '--config-file=/etc/designate/conf.d/nova_sink.cfg') 180 | self.assertEqual( 181 | a.neutron_conf_args, 182 | '--config-file=/etc/designate/conf.d/neutron_sink.cfg') 183 | self.patch(designate.os.path, 'exists', return_value=False) 184 | self.assertEqual(a.nova_conf_args, '') 185 | self.assertEqual(a.neutron_conf_args, '') 186 | 187 | def test_rndc_master_ip(self): 188 | relation = mock.MagicMock() 189 | self.patch( 190 | designate.openstack_adapters.APIConfigurationAdapter, 191 | 'get_network_addresses') 192 | self.patch(designate.os_ip, 'resolve_address', return_value='intip') 193 | a = designate.DesignateConfigurationAdapter(relation) 194 | self.assertEqual(a.rndc_master_ip, 'intip') 195 | 196 | def test_also_notifies_hosts(self): 197 | relation = mock.MagicMock 198 | test_config = { 199 | 'also-notifies': '10.0.0.1:53 10.0.0.2:10053', 200 | } 201 | with mock.patch.object(designate.hookenv, 'config', 202 | side_effect=FakeConfig(test_config)): 203 | expect = [{'address': '10.0.0.1', 204 | 'port': '53'}, 205 | {'address': '10.0.0.2', 206 | 'port': '10053'}] 207 | a = designate.DesignateConfigurationAdapter(relation) 208 | self.assertEqual(a.also_notifies_hosts, expect) 209 | 210 | 211 | class TestDesignateCharm(Helper): 212 | 213 | def test_install(self): 214 | self.patch(designate.DesignateCharm, 'configure_source') 215 | self.patch(designate.DesignateCharm, 'update_api_ports') 216 | self.ch_config.side_effect = lambda: {'openstack-origin': 'distro'} 217 | a = designate.DesignateCharm(release='mitaka') 218 | a.install() 219 | self.configure_source.assert_called_with() 220 | 221 | def test_render_base_config(self): 222 | self.patch(designate.DesignateCharm, 'haproxy_enabled') 223 | self.patch( 224 | designate.DesignateCharm, 225 | 'render_with_interfaces') 226 | self.patch_object(designate.DesignateCharm, 'haproxy_enabled', 227 | new=lambda x: True) 228 | a = designate.DesignateCharm(release='mitaka') 229 | a.render_base_config('interface_list') 230 | expect_configs = [ 231 | '/root/novarc', 232 | '/etc/designate/designate.conf', 233 | '/etc/designate/rndc.key', 234 | '/etc/default/openstack', 235 | '/etc/haproxy/haproxy.cfg'] 236 | self.render_with_interfaces.assert_called_with( 237 | 'interface_list', 238 | configs=expect_configs) 239 | 240 | def test_render_full_config(self): 241 | self.patch( 242 | designate.DesignateCharm, 243 | 'render_with_interfaces') 244 | a = designate.DesignateCharm(release='mitaka') 245 | a.render_full_config('interface_list') 246 | self.render_with_interfaces.assert_called_with('interface_list') 247 | 248 | def test_write_key_file(self): 249 | self.patch(designate.host, 'write_file') 250 | a = designate.DesignateCharm(release='mitaka') 251 | a.write_key_file('unit1', 'keydigest') 252 | self.write_file.assert_called_with( 253 | '/etc/designate/rndc_unit1.key', 254 | mock.ANY, 255 | owner='root', 256 | group='designate', 257 | perms=0o440) 258 | 259 | def test_render_rndc_keys(self): 260 | test_config = { 261 | 'dns-slaves': '10.0.0.10:port1:key1 192.168.23.4:port2:key2', 262 | } 263 | self.patch(designate.DesignateCharm, 'write_key_file') 264 | with mock.patch.object(designate.hookenv, 'config', 265 | side_effect=FakeConfig(test_config)): 266 | a = designate.DesignateCharm(release='mitaka') 267 | a.render_rndc_keys() 268 | calls = [ 269 | mock.call('10_0_0_10', 'key1'), 270 | mock.call('192_168_23_4', 'key2'), 271 | ] 272 | self.write_key_file.assert_has_calls(calls) 273 | 274 | def test_rndc_keys(self): 275 | 276 | def fake_conversations(): 277 | conversations = [] 278 | Conversation = mock.Mock() 279 | Conversation.key = 'reactive.conversations.dns-backend:65.' 280 | 'designate-bind-t1/1' 281 | Conversation.namespace = 'dns-backend:65' 282 | self.patch(Conversation, 'relation_ids', 283 | return_value='dns-backend:65') 284 | Conversation.relation_name = 'dns-backend' 285 | Conversation.scope = 'designate-bind-t1/1' 286 | self.patch(Conversation, 'get_remote', return_value='rndckey1') 287 | conversations.append(Conversation) 288 | Conversation = mock.Mock() 289 | Conversation.key = 'reactive.conversations.dns-backend:66.' 290 | 'designate-bind-t0/1' 291 | Conversation.namespace = 'dns-backend:66' 292 | self.patch(Conversation, 'relation_ids', 293 | return_value='dns-backend:66') 294 | Conversation.relation_name = 'dns-backend' 295 | Conversation.scope = 'designate-bind-t0/1' 296 | self.patch(Conversation, 'get_remote', return_value='rndckey2') 297 | conversations.append(Conversation) 298 | return conversations 299 | 300 | mock_endpoint_from_flag = mock.MagicMock() 301 | mock_endpoint_from_flag.conversations.side_effect = fake_conversations 302 | 303 | def fake_endpoint_from_flag(*args, **kwargs): 304 | return mock_endpoint_from_flag 305 | 306 | relation = mock.MagicMock() 307 | self.patch(designate.DesignateCharm, 'write_key_file') 308 | self.patch(designate.relations, 'endpoint_from_flag', 309 | side_effect=fake_endpoint_from_flag) 310 | 311 | designate.DesignateConfigurationAdapter(relation) 312 | d = designate.DesignateCharm() 313 | d.render_relation_rndc_keys() 314 | calls = [ 315 | mock.call('designate_bind_t1', 'rndckey1'), 316 | mock.call('designate_bind_t0', 'rndckey2'), 317 | ] 318 | 319 | self.write_key_file.assert_has_calls(calls) 320 | 321 | def test_get_domain_id(self): 322 | self.patch(designate.DesignateCharm, 'ensure_api_responding') 323 | self.patch(designate.subprocess, 'check_output') 324 | self.check_output.return_value = b'hi\n' 325 | self.assertEqual(designate.DesignateCharm.get_domain_id('domain'), 326 | 'hi') 327 | self.check_output.assert_called_with( 328 | ['reactive/designate_utils.py', 329 | 'domain-get', '--domain-name', 'domain']) 330 | 331 | def test_create_domain(self): 332 | self.patch(designate.DesignateCharm, 'ensure_api_responding') 333 | self.patch(designate.subprocess, 'check_call') 334 | designate.DesignateCharm.create_domain('domain', 'email') 335 | self.check_call.assert_called_with( 336 | ['reactive/designate_utils.py', 337 | 'domain-create', '--domain-name', 'domain', 338 | '--email', 'email']) 339 | 340 | def test_create_server(self): 341 | self.patch(designate.subprocess, 'check_call') 342 | self.patch(designate.DesignateCharm, 'ensure_api_responding') 343 | designate.DesignateCharm.create_server('nameservername') 344 | self.check_call.assert_called_with( 345 | ['reactive/designate_utils.py', 346 | 'server-create', '--server-name', 347 | 'nameservername']) 348 | 349 | def test_domain_init_done(self): 350 | self.patch(designate.hookenv, 'leader_get') 351 | self.leader_get.return_value = True 352 | a = designate.DesignateCharm(release='mitaka') 353 | self.assertTrue(a.domain_init_done()) 354 | self.leader_get.return_value = False 355 | a = designate.DesignateCharm(release='mitaka') 356 | self.assertFalse(a.domain_init_done()) 357 | 358 | def test_create_initial_servers_and_domains(self): 359 | test_config = { 360 | 'nameservers': 'dnsserverrec1. dnsserverrec2', 361 | 'nova-domain': 'novadomain', 362 | 'nova-domain-email': 'novaemail', 363 | 'neutron-domain': 'neutrondomain', 364 | 'neutron-domain-email': 'neutronemail', 365 | } 366 | self.patch(designate.DesignateCharm, 'ensure_api_responding') 367 | self.ensure_api_responding.return_value = True 368 | self.patch(designate.hookenv, 'is_leader', return_value=True) 369 | self.patch(designate.hookenv, 'leader_set') 370 | self.patch(designate.hookenv, 'leader_get', return_value=False) 371 | self.patch(designate.DesignateCharm, 'create_server') 372 | self.patch(designate.DesignateCharm, 'create_domain') 373 | 374 | @contextlib.contextmanager 375 | def fake_check_zone_ids(a, b): 376 | yield 377 | self.patch(designate.DesignateCharm, 'check_zone_ids', 378 | new=fake_check_zone_ids) 379 | with mock.patch.object(designate.hookenv, 'config', 380 | side_effect=FakeConfig(test_config)): 381 | designate.DesignateCharm.create_initial_servers_and_domains() 382 | self.create_server.assert_has_calls([mock.call('dnsserverrec1.'), 383 | mock.call('dnsserverrec2.')]) 384 | calls = [ 385 | mock.call('novadomain', 'novaemail'), 386 | mock.call('neutrondomain', 'neutronemail')] 387 | self.create_domain.assert_has_calls(calls) 388 | 389 | def test_check_zone_ids_change(self): 390 | self.patch(designate.hookenv, 'leader_set') 391 | DOMAIN_LOOKSUPS = ['novaid1', 'neutronid1', 'novaid1', 'neutronid2'] 392 | 393 | def fake_get_domain_id(a): 394 | return DOMAIN_LOOKSUPS.pop() 395 | self.patch(designate.DesignateCharm, 'get_domain_id', 396 | side_effect=fake_get_domain_id) 397 | with designate.DesignateCharm.check_zone_ids('novadom', 'neutrondom'): 398 | pass 399 | self.leader_set.assert_called_once_with({'domain-init-done': mock.ANY}) 400 | 401 | def test_check_zone_ids_nochange(self): 402 | self.patch(designate.hookenv, 'leader_set') 403 | DOMAIN_LOOKSUPS = ['novaid1', 'neutronid1', 'novaid1', 'neutronid1'] 404 | 405 | def fake_get_domain_id(a): 406 | return DOMAIN_LOOKSUPS.pop() 407 | self.patch(designate.DesignateCharm, 'get_domain_id', 408 | side_effect=fake_get_domain_id) 409 | with designate.DesignateCharm.check_zone_ids('novadom', 'neutrondom'): 410 | pass 411 | self.assertFalse(self.leader_set.called) 412 | 413 | def test_render_nrpe(self): 414 | self.patch_object(designate.nrpe, 'add_init_service_checks') 415 | charm_instance = designate.DesignateCharm(release='queens') 416 | charm_instance.render_nrpe() 417 | self.add_init_service_checks.assert_has_calls([ 418 | mock.call().add_init_service_checks( 419 | mock.ANY, 420 | charm_instance.services, 421 | mock.ANY 422 | ), 423 | ]) 424 | 425 | def test_add_nrpe_nameserver_checks(self): 426 | test_config = { 427 | 'nameservers': '8.8.8.8. 9.9.9.9. ns1-example.com.', 428 | 'nrpe-nameserver-check-host': 'canonical.com', 429 | } 430 | charm_instance = designate.DesignateCharm(release='queens') 431 | self.patch_object(designate.hookenv, 'config') 432 | self.config.return_value = test_config 433 | self.patch_object(designate.nrpe, 'NRPE') 434 | nrpe_mock = mock.MagicMock() 435 | self.NRPE.return_value = nrpe_mock 436 | charm_instance.add_nrpe_nameserver_checks() 437 | nrpe_mock.add_check.assert_has_calls([ 438 | mock.call( 439 | 'nameserver-8.8.8.8', 440 | 'Check the upstream DNS server.', 441 | 'check_dns -H canonical.com -s 8.8.8.8', 442 | ), 443 | mock.call( 444 | 'nameserver-9.9.9.9', 445 | 'Check the upstream DNS server.', 446 | 'check_dns -H canonical.com -s 9.9.9.9', 447 | ), 448 | mock.call( 449 | 'nameserver-ns1-example.com', 450 | 'Check the upstream DNS server.', 451 | 'check_dns -H canonical.com -s ns1-example.com', 452 | ), 453 | ]) 454 | nrpe_mock.write.assert_called_once_with() 455 | 456 | def test_disable_add_nrpe_nameserver_checks(self): 457 | test_config = { 458 | 'nameservers': '8.8.8.8. 9.9.9.9. ns1-example.com.', 459 | 'nrpe-nameserver-check-host': '', 460 | } 461 | charm_instance = designate.DesignateCharm(release='queens') 462 | self.patch_object(designate.hookenv, 'config') 463 | self.config.return_value = test_config 464 | self.patch_object(designate.nrpe, 'NRPE') 465 | nrpe_mock = mock.MagicMock() 466 | self.NRPE.return_value = nrpe_mock 467 | charm_instance.add_nrpe_nameserver_checks() 468 | nrpe_mock.add_check.assert_has_calls([ 469 | ]) 470 | nrpe_mock.write.assert_called_once_with() 471 | 472 | def test_add_nrpe_nameserver_checks_custom_host(self): 473 | test_config = { 474 | 'nameservers': '8.8.8.8. 9.9.9.9. ns1-example.com.', 475 | 'nrpe-nameserver-check-host': 'test.xyz', 476 | } 477 | charm_instance = designate.DesignateCharm(release='queens') 478 | self.patch_object(designate.hookenv, 'config') 479 | self.config.return_value = test_config 480 | self.patch_object(designate.nrpe, 'NRPE') 481 | nrpe_mock = mock.MagicMock() 482 | self.NRPE.return_value = nrpe_mock 483 | charm_instance.add_nrpe_nameserver_checks() 484 | nrpe_mock.add_check.assert_has_calls([ 485 | mock.call( 486 | 'nameserver-8.8.8.8', 487 | 'Check the upstream DNS server.', 488 | 'check_dns -H test.xyz -s 8.8.8.8', 489 | ), 490 | mock.call( 491 | 'nameserver-9.9.9.9', 492 | 'Check the upstream DNS server.', 493 | 'check_dns -H test.xyz -s 9.9.9.9', 494 | ), 495 | mock.call( 496 | 'nameserver-ns1-example.com', 497 | 'Check the upstream DNS server.', 498 | 'check_dns -H test.xyz -s ns1-example.com', 499 | ), 500 | ]) 501 | nrpe_mock.write.assert_called_once_with() 502 | 503 | def test_remove_nrpe_nameserver_checks_only_host_changed(self): 504 | charm_instance = designate.DesignateCharm(release='queens') 505 | self.patch_object(designate.hookenv, 'config') 506 | config_mock = mock.MagicMock() 507 | config_mock.changed.side_effect = ( 508 | lambda key: key == 'nrpe-nameserver-check-host') 509 | config_mock.previous.return_value = 'previous-ns-1 previous-ns-2.' 510 | self.config.return_value = config_mock 511 | self.patch_object(designate.nrpe, 'NRPE') 512 | nrpe_mock = mock.MagicMock() 513 | self.NRPE.return_value = nrpe_mock 514 | charm_instance.remove_nrpe_nameserver_checks() 515 | nrpe_mock.remove_check.assert_has_calls([ 516 | mock.call( 517 | shortname='nameserver-previous-ns-1' 518 | ), 519 | mock.call( 520 | shortname='nameserver-previous-ns-2' 521 | ), 522 | ]) 523 | nrpe_mock.write.assert_called_once_with() 524 | 525 | def test_remove_nrpe_nameserver_checks(self): 526 | charm_instance = designate.DesignateCharm(release='queens') 527 | self.patch_object(designate.hookenv, 'config') 528 | config_mock = mock.MagicMock() 529 | config_mock.changed.return_value = True 530 | config_mock.previous.return_value = 'previous-ns-1. previous-ns-2.' 531 | self.config.return_value = config_mock 532 | self.patch_object(designate.nrpe, 'NRPE') 533 | nrpe_mock = mock.MagicMock() 534 | self.NRPE.return_value = nrpe_mock 535 | charm_instance.remove_nrpe_nameserver_checks() 536 | nrpe_mock.remove_check.assert_has_calls([ 537 | mock.call( 538 | shortname='nameserver-previous-ns-1' 539 | ), 540 | mock.call( 541 | shortname='nameserver-previous-ns-2' 542 | ), 543 | ]) 544 | nrpe_mock.write.assert_called_once_with() 545 | 546 | 547 | class TestDesignateQueensCharm(Helper): 548 | 549 | def test_upgrade(self): 550 | self.patch(designate.DesignateCharm, 'run_upgrade') 551 | self.patch(designate.relations, 'endpoint_from_flag') 552 | endpoint = mock.MagicMock() 553 | self.endpoint_from_flag.return_value = endpoint 554 | a = designate.DesignateCharmQueens(release='queens') 555 | a.run_upgrade() 556 | self.run_upgrade.assert_called_once_with(interfaces_list=None) 557 | endpoint.request_restart.assert_called_once_with() 558 | -------------------------------------------------------------------------------- /src/lib/charm/openstack/designate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Canonical Ltd 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import collections 16 | import contextlib 17 | import os 18 | import subprocess 19 | import uuid 20 | 21 | import charmhelpers.contrib.openstack.utils as ch_utils 22 | import charmhelpers.contrib.charmsupport.nrpe as nrpe 23 | import charms_openstack.adapters as openstack_adapters 24 | import charms_openstack.charm as openstack_charm 25 | import charms_openstack.ip as os_ip 26 | import charms_openstack.plugins as ch_plugins 27 | import charmhelpers.core.decorators as decorators 28 | import charmhelpers.core.hookenv as hookenv 29 | import charmhelpers.core.host as host 30 | import charms.reactive.relations as relations 31 | 32 | from charmhelpers.contrib.network import ip as ch_ip 33 | 34 | DESIGNATE_DIR = '/etc/designate' 35 | DESIGNATE_DEFAULT = '/etc/default/openstack' 36 | DESIGNATE_CONF = DESIGNATE_DIR + '/designate.conf' 37 | POOLS_YAML = DESIGNATE_DIR + '/pools.yaml' 38 | RNDC_KEY_CONF = DESIGNATE_DIR + '/rndc.key' 39 | NOVA_SINK_FILE = DESIGNATE_DIR + '/conf.d/nova_sink.cfg' 40 | NEUTRON_SINK_FILE = DESIGNATE_DIR + '/conf.d/neutron_sink.cfg' 41 | RC_FILE = '/root/novarc' 42 | openstack_charm.use_defaults( 43 | 'charm.default-select-release', 44 | 'upgrade-charm', 45 | ) 46 | 47 | 48 | class DesignateDBAdapter(openstack_adapters.DatabaseRelationAdapter): 49 | """Get database URIs for the two designate databases""" 50 | 51 | @property 52 | def designate_uri(self): 53 | """URI for designate DB""" 54 | return self.get_uri(prefix='designate') 55 | 56 | @property 57 | def designate_pool_uri(self): 58 | """URI for designate pool DB""" 59 | return self.get_uri(prefix='dpm') 60 | 61 | 62 | class BindRNDCRelationAdapter(openstack_adapters.OpenStackRelationAdapter): 63 | 64 | interface_type = "dns" 65 | 66 | @property 67 | def slave_ips(self): 68 | """List of DNS slave address infoprmation 69 | 70 | @returns: list [{'unit': unitname, 'address': 'address'}, 71 | ...] 72 | """ 73 | return self.relation.slave_ips() 74 | 75 | @property 76 | def pool_config(self): 77 | """List of DNS slave information from Juju attached DNS slaves 78 | 79 | Creates a dict for each backends and returns a list of those dicts. 80 | The designate config file has a section per backend. The template uses 81 | the nameserver and pool_target names to create a section for each 82 | backend 83 | 84 | @returns: list [{'nameserver': name, 'pool_target': name, 85 | 'address': slave_ip_addr}, 86 | ...] 87 | """ 88 | pconfig = [] 89 | for slave in self.slave_ips: 90 | application_name = slave['unit'].split('/')[0].replace('-', '_') 91 | pconfig.append({ 92 | 'nameserver': 'nameserver_{}'.format(application_name), 93 | 'pool_target': 'nameserver_{}'.format(application_name), 94 | 'address': slave['address'], 95 | 'rndc_key_file': '/etc/designate/rndc_{}.key'.format( 96 | application_name), 97 | }) 98 | return pconfig 99 | 100 | @property 101 | def pool_targets(self): 102 | """List of pool_target section names 103 | 104 | @returns: str Comma delimited list of pool_target section names 105 | """ 106 | return ', '.join([s['pool_target'] for s in self.pool_config]) 107 | 108 | @property 109 | def slave_addresses(self): 110 | """List of slave IP addresses 111 | 112 | @returns: str Comma delimited list of slave IP addresses 113 | """ 114 | return ', '.join(['{}:53'.format(s['address']) 115 | for s in self.pool_config]) 116 | 117 | @property 118 | def rndc_info(self): 119 | """Rndc key and algorith in formation. 120 | 121 | @returns: dict {'algorithm': rndc_algorithm, 122 | 'secret': rndc_secret_digest} 123 | """ 124 | return self.relation.rndc_info 125 | 126 | 127 | class DesignateConfigurationAdapter( 128 | openstack_adapters.APIConfigurationAdapter): 129 | 130 | @property 131 | def pool_config(self): 132 | """List of DNS slave information from user defined config 133 | 134 | Creates a dict for each backends and returns a list of those dicts. 135 | The designate config file has a section per backend. The template uses 136 | the nameserver and pool_target names to create a section for each 137 | backend. 138 | 139 | @returns: list [{'nameserver': name, 140 | 'pool_target': name, 141 | 'address': slave_ip_addr, 142 | 'rndc_key_file': rndc_key_file}, 143 | ...] 144 | """ 145 | pconfig = [] 146 | if self.dns_slaves: 147 | for entry in self.dns_slaves.split(): 148 | try: 149 | address, port, key = entry.split(':') 150 | unit_name = address.replace('.', '_') 151 | pconfig.append({ 152 | 'nameserver': 'nameserver_{}'.format(unit_name), 153 | 'pool_target': 'nameserver_{}'.format(unit_name), 154 | 'address': address, 155 | 'rndc_key_file': '/etc/designate/rndc_{}.key'.format( 156 | unit_name), 157 | }) 158 | except ValueError: 159 | # the entry doesn't until 3 values, so ignore it. 160 | pass 161 | return pconfig 162 | 163 | def invalid_pool_config(self): 164 | """Validates that the pool config at least looks like something that 165 | can be used. 166 | 167 | @returns: Error string or None if okay 168 | """ 169 | if self.dns_slaves: 170 | for entry in self.dns_slaves.split(): 171 | try: 172 | _, __, ___ = entry.split(':') 173 | except ValueError: 174 | return "dns_slaves is malformed" 175 | return None 176 | 177 | @property 178 | def pool_targets(self): 179 | """List of pool_target section names 180 | 181 | @returns: str Comma delimited list of pool_target section names 182 | """ 183 | return ', '.join([s['pool_target'] for s in self.pool_config]) 184 | 185 | @property 186 | def slave_addresses(self): 187 | """List of slave IP addresses 188 | 189 | @returns: str Comma delimited list of slave IP addresses 190 | """ 191 | return ', '.join(['{}:53'.format(s['address']) 192 | for s in self.pool_config]) 193 | 194 | @property 195 | def nova_domain_id(self): 196 | """Returns the id of the domain corresponding to the user supplied 197 | 'nova-domain' 198 | 199 | @returns nova domain id 200 | """ 201 | domain = hookenv.config('nova-domain') 202 | if domain: 203 | return DesignateCharm.get_domain_id(domain) 204 | return None 205 | 206 | @property 207 | def neutron_domain_id(self): 208 | """Returns the id of the domain corresponding to the user supplied 209 | 'neutron-domain' 210 | 211 | @returns neutron domain id 212 | """ 213 | domain = hookenv.config('neutron-domain') 214 | if domain: 215 | return DesignateCharm.get_domain_id(domain) 216 | return None 217 | 218 | @property 219 | def notification_handlers(self): 220 | handlers = [] 221 | if os.path.exists(NOVA_SINK_FILE): 222 | handlers.append('nova_fixed') 223 | if os.path.exists(NEUTRON_SINK_FILE): 224 | handlers.append('neutron_floatingip') 225 | return ','.join(handlers) 226 | 227 | @property 228 | def nova_conf_args(self): 229 | """Returns config file directive to point daemons at nova config file. 230 | These directives are designed to be used in /etc/default/ files 231 | 232 | @returns startup config file option 233 | """ 234 | daemon_arg = '' 235 | if os.path.exists(NOVA_SINK_FILE): 236 | daemon_arg = '--config-file={}'.format(NOVA_SINK_FILE) 237 | return daemon_arg 238 | 239 | @property 240 | def neutron_conf_args(self): 241 | """Returns config file directive to point daemons at neutron config 242 | file. These directives are designed to be used in /etc/default/ files 243 | 244 | @returns startup config file option 245 | """ 246 | daemon_arg = '' 247 | if os.path.exists(NEUTRON_SINK_FILE): 248 | daemon_arg = '--config-file={}'.format(NEUTRON_SINK_FILE) 249 | return daemon_arg 250 | 251 | @property 252 | def rndc_master_ip(self): 253 | """Returns IP address slave DNS slave should use to query master 254 | """ 255 | return os_ip.resolve_address(endpoint_type=os_ip.INTERNAL) 256 | 257 | @property 258 | def rndc_master_ips(self): 259 | rndc_master_ips = [] 260 | rndc_master_ip = ch_ip.get_relation_ip('dns-backend') 261 | rndc_master_ips.append(rndc_master_ip) 262 | cluster_relid = hookenv.relation_ids('cluster')[0] 263 | if hookenv.related_units(relid=cluster_relid): 264 | for unit in hookenv.related_units(relid=cluster_relid): 265 | rndc_master_ip = hookenv.relation_get('rndc-address', 266 | rid=cluster_relid, 267 | unit=unit) 268 | if rndc_master_ip is not None: 269 | rndc_master_ips.append(rndc_master_ip) 270 | return rndc_master_ips 271 | 272 | @property 273 | def ns_records(self): 274 | """List of NS records 275 | 276 | @returns [] List of NS records 277 | """ 278 | return self.nameservers.split() 279 | 280 | @property 281 | def also_notifies_hosts(self): 282 | also_notifies_hosts = [] 283 | if hookenv.config('also-notifies'): 284 | for entry in hookenv.config('also-notifies').split(): 285 | address, port = entry.split(':') 286 | also_notifies_hosts.append({'address': address, 'port': port}) 287 | return also_notifies_hosts 288 | 289 | 290 | class DesignateAdapters(openstack_adapters.OpenStackAPIRelationAdapters): 291 | """ 292 | Adapters class for the Designate charm. 293 | """ 294 | relation_adapters = { 295 | 'shared_db': DesignateDBAdapter, 296 | 'cluster': openstack_adapters.PeerHARelationAdapter, 297 | 'dns_backend': BindRNDCRelationAdapter, 298 | 'coordinator_memcached': openstack_adapters.MemcacheRelationAdapter, 299 | } 300 | 301 | 302 | # note plugin comes first to override the config_changed method as a mixin 303 | class DesignateCharm(ch_plugins.PolicydOverridePlugin, 304 | openstack_charm.HAOpenStackCharm): 305 | """Designate charm""" 306 | 307 | name = 'designate' 308 | packages = ['designate-agent', 'designate-api', 'designate-central', 309 | 'designate-common', 'designate-mdns', 310 | 'designate-pool-manager', 'designate-sink', 311 | 'designate-zone-manager', 'bind9utils', 'python-apt'] 312 | 313 | services = ['designate-mdns', 'designate-zone-manager', 314 | 'designate-agent', 'designate-pool-manager', 315 | 'designate-central', 'designate-sink', 316 | 'designate-api'] 317 | 318 | deprecated_services = [] 319 | 320 | api_ports = { 321 | 'designate-api': { 322 | os_ip.PUBLIC: 9001, 323 | os_ip.ADMIN: 9001, 324 | os_ip.INTERNAL: 9001, 325 | } 326 | } 327 | 328 | healthcheck = { 329 | 'option': 'httpchk GET /healthcheck', 330 | 'http-check': 'expect status 200', 331 | } 332 | 333 | required_relations = ['shared-db', 'amqp', 'identity-service', 334 | 'coordinator-memcached'] 335 | 336 | restart_map = { 337 | '/etc/default/openstack': services, 338 | '/etc/designate/designate.conf': services, 339 | '/etc/designate/rndc.key': services, 340 | '/etc/designate/conf.d/nova_sink.cfg': services, 341 | '/etc/designate/conf.d/neutron_sink.cfg': services, 342 | POOLS_YAML: ['designate-pool-manager'], 343 | RC_FILE: [''], 344 | } 345 | service_type = 'designate' 346 | default_service = 'designate-api' 347 | sync_cmd = ['designate-manage', 'database', 'sync'] 348 | adapters_class = DesignateAdapters 349 | configuration_class = DesignateConfigurationAdapter 350 | 351 | ha_resources = ['vips', 'haproxy', 'dnsha'] 352 | release = 'mitaka' 353 | release_pkg = 'designate-common' 354 | package_codenames = { 355 | 'designate-common': collections.OrderedDict([ 356 | ('2', 'mitaka'), 357 | ('3', 'newton'), 358 | ('4', 'ocata'), 359 | ('5', 'pike'), 360 | ('6', 'queens'), 361 | ('7', 'rocky'), 362 | ('8', 'stein'), 363 | ('9', 'train'), 364 | ('10', 'ussuri'), 365 | ('11', 'victoria'), 366 | ('12', 'wallaby'), 367 | ]), 368 | } 369 | 370 | group = 'designate' 371 | 372 | # policyd override constants 373 | policyd_service_name = 'designate' 374 | 375 | def install(self): 376 | """Customise the installation, configure the source and then call the 377 | parent install() method to install the packages 378 | """ 379 | self.configure_source() 380 | super(DesignateCharm, self).install() 381 | 382 | def render_base_config(self, interfaces_list): 383 | """Render initial config to bootstrap Designate service 384 | 385 | @returns None 386 | """ 387 | configs = [RC_FILE, DESIGNATE_CONF, RNDC_KEY_CONF, DESIGNATE_DEFAULT] 388 | if self.haproxy_enabled(): 389 | configs.append(self.HAPROXY_CONF) 390 | self.render_with_interfaces( 391 | interfaces_list, 392 | configs=configs) 393 | 394 | def render_full_config(self, interfaces_list): 395 | """Render all config for Designate service 396 | 397 | @returns None 398 | """ 399 | # Render base config first to ensure Designate API is responding as 400 | # sink configs rely on it. 401 | self.render_base_config(interfaces_list) 402 | self.render_with_interfaces(interfaces_list) 403 | 404 | def write_key_file(self, unit_name, key): 405 | """Write rndc keyfile for given unit_name 406 | 407 | @param unit_name: str Name of unit using key 408 | @param key: str RNDC key 409 | @returns None 410 | """ 411 | key_file = '/etc/designate/rndc_{}.key'.format(unit_name) 412 | template = ('key "rndc-key" {{\n algorithm hmac-md5;\n ' 413 | 'secret "{}";\n}};') 414 | host.write_file( 415 | key_file, 416 | str.encode(template.format(key)), 417 | owner='root', 418 | group='designate', 419 | perms=0o440) 420 | 421 | def render_rndc_keys(self): 422 | """Render the rndc keys supplied via user config 423 | 424 | @returns None 425 | """ 426 | slaves = hookenv.config('dns-slaves') or '' 427 | try: 428 | for entry in slaves.split(): 429 | address, port, key = entry.split(':') 430 | unit_name = address.replace('.', '_') 431 | self.write_key_file(unit_name, key) 432 | except ValueError as e: 433 | hookenv.log("Problem with 'dns-slaves' config: {}" 434 | .format(str(e)), level=hookenv.ERROR) 435 | 436 | def render_relation_rndc_keys(self): 437 | """Render the rndc keys for each application in the dns-backend 438 | relation 439 | 440 | @returns None 441 | """ 442 | try: 443 | applications = [] 444 | dns_backend = relations.endpoint_from_flag( 445 | 'dns-backend.available').conversations() 446 | for conversation in dns_backend: 447 | application_name = conversation.scope.split( 448 | '/')[0].replace('-', '_') 449 | if application_name not in applications: 450 | applications.append(application_name) 451 | rndckey = conversation.get_remote('rndckey') 452 | self.write_key_file(application_name, rndckey) 453 | 454 | except ValueError as e: 455 | hookenv.log("problem writing relation_rndc_keys: {}" 456 | .format(str(e)), level=hookenv.ERROR) 457 | 458 | def configure_sink(self): 459 | cmp_os_release = ch_utils.CompareOpenStackReleases( 460 | self.release 461 | ) 462 | return cmp_os_release < 'queens' 463 | 464 | @classmethod 465 | @decorators.retry_on_exception( 466 | 40, base_delay=5, exc_type=subprocess.CalledProcessError) 467 | def get_domain_id(cls, domain): 468 | """Return the domain ID for a given domain name 469 | 470 | @param domain: Domain name 471 | @returns domain_id 472 | """ 473 | if domain: 474 | cls.ensure_api_responding() 475 | get_cmd = ['reactive/designate_utils.py', 'domain-get', 476 | '--domain-name', domain] 477 | output = subprocess.check_output(get_cmd) 478 | if output: 479 | return output.decode('utf8').strip() 480 | return None 481 | 482 | @classmethod 483 | @decorators.retry_on_exception( 484 | 40, base_delay=5, exc_type=subprocess.CalledProcessError) 485 | def create_domain(cls, domain, email): 486 | """Create a domain 487 | 488 | @param domain: The name of the domain you are creating. The name must 489 | end with a full stop. 490 | @param email: An email address of the person responsible for the 491 | domain. 492 | @returns None 493 | """ 494 | cls.ensure_api_responding() 495 | create_cmd = ['reactive/designate_utils.py', 'domain-create', 496 | '--domain-name', domain, '--email', email] 497 | subprocess.check_call(create_cmd) 498 | 499 | @classmethod 500 | @decorators.retry_on_exception( 501 | 40, base_delay=5, exc_type=subprocess.CalledProcessError) 502 | def create_server(cls, nsname): 503 | """ create a nameserver entry with the supplied name 504 | 505 | @param nsname: Name of NameserverS record 506 | @returns None 507 | """ 508 | cls.ensure_api_responding() 509 | create_cmd = ['reactive/designate_utils.py', 'server-create', 510 | '--server-name', nsname] 511 | subprocess.check_call(create_cmd) 512 | 513 | def domain_init_done(self): 514 | """Query leader db to see if domain creation is donei 515 | 516 | @returns boolean""" 517 | return hookenv.leader_get(attribute='domain-init-done') 518 | 519 | @classmethod 520 | @decorators.retry_on_exception( 521 | 40, base_delay=5, exc_type=subprocess.CalledProcessError) 522 | def ensure_api_responding(cls): 523 | """Check that the api service is responding. 524 | 525 | The retry_on_exception decorator will cause this method to be called 526 | until it succeeds or retry limit is exceeded""" 527 | hookenv.log('Checking API service is responding', 528 | level=hookenv.WARNING) 529 | check_cmd = ['reactive/designate_utils.py', 'server-list'] 530 | subprocess.check_call(check_cmd) 531 | 532 | @classmethod 533 | @contextlib.contextmanager 534 | def check_zone_ids(cls, nova_domain_name, neutron_domain_name): 535 | zone_org_ids = { 536 | 'nova-domain-id': cls.get_domain_id(nova_domain_name), 537 | 'neutron-domain-id': cls.get_domain_id(neutron_domain_name), 538 | } 539 | yield 540 | zone_ids = { 541 | 'nova-domain-id': cls.get_domain_id(nova_domain_name), 542 | 'neutron-domain-id': cls.get_domain_id(neutron_domain_name), 543 | } 544 | if zone_org_ids != zone_ids: 545 | # Update leader-db to trigger peers to rerender configs 546 | # as sink files will need updating with new domain ids 547 | # Use host ID and current time UUID to help with debugging 548 | hookenv.leader_set({'domain-init-done': uuid.uuid1()}) 549 | 550 | @classmethod 551 | def create_initial_servers_and_domains(cls): 552 | """Create the nameserver entry and domains based on the charm user 553 | supplied config 554 | 555 | NOTE(AJK): This only wants to be done ONCE and by the leader, so we use 556 | leader settings to store that we've done it, after it's successfully 557 | completed. 558 | 559 | @returns None 560 | """ 561 | KEY = 'create_initial_servers_and_domains' 562 | if hookenv.is_leader() and not hookenv.leader_get(KEY): 563 | nova_domain_name = hookenv.config('nova-domain') 564 | neutron_domain_name = hookenv.config('neutron-domain') 565 | with cls.check_zone_ids(nova_domain_name, neutron_domain_name): 566 | if hookenv.config('nameservers'): 567 | for ns in hookenv.config('nameservers').split(): 568 | ns_ = ns 569 | if not ns.endswith('.'): 570 | ns_ = ns + '.' 571 | hookenv.log(("Missing dot (.) at the end of '%s', " 572 | "adding it automatically." % ns), 573 | level=hookenv.WARNING) 574 | cls.create_server(ns_) 575 | else: 576 | hookenv.log('No nameserver specified, skipping creation of' 577 | 'nova and neutron domains', 578 | level=hookenv.WARNING) 579 | return 580 | if nova_domain_name: 581 | cls.create_domain( 582 | nova_domain_name, 583 | hookenv.config('nova-domain-email')) 584 | if neutron_domain_name: 585 | cls.create_domain( 586 | neutron_domain_name, 587 | hookenv.config('neutron-domain-email')) 588 | # if this fails, we weren't the leader any more; another unit may 589 | # attempt to do this too. 590 | hookenv.leader_set({KEY: 'done'}) 591 | 592 | def update_pools(self): 593 | # designate-manage communicates with designate via message bus so no 594 | # need to set OS_ vars 595 | # NOTE(AJK) this runs with every hook (once most relations are up) and 596 | # so if it fails it will be picked up by the next relation change or 597 | # update-status. i.e. it will heal eventually. 598 | if hookenv.is_leader(): 599 | try: 600 | cmd = "designate-manage pool update" 601 | # Note(tinwood) that this command may fail if the pools.yaml 602 | # doesn't actually contain any pools. This happens when the 603 | # relation is broken, which errors out the charm. This stops 604 | # this happening and logs the error. 605 | subprocess.check_call(cmd.split(), timeout=60) 606 | # Update leader db to trigger restarts 607 | hookenv.leader_set( 608 | {'pool-yaml-hash': host.file_hash(POOLS_YAML)}) 609 | except subprocess.CalledProcessError as e: 610 | hookenv.log("designate-manage pool update failed: {}" 611 | .format(str(e))) 612 | except subprocess.TimeoutExpired as e: 613 | # the timeout is if the rabbitmq server has gone away; it just 614 | # retries continuously; this lets the hook complete. 615 | hookenv.log("designate-manage pool command timed out: {}". 616 | format(str(e))) 617 | 618 | def custom_assess_status_check(self): 619 | if self.configure_sink(): 620 | if (not hookenv.config('nameservers') and 621 | (hookenv.config('nova-domain') or 622 | hookenv.config('neutron-domain'))): 623 | return 'blocked', ('nameservers must be set when specifying' 624 | ' nova-domain or neutron-domain') 625 | invalid_dns = self.options.invalid_pool_config() 626 | if invalid_dns: 627 | return 'blocked', invalid_dns 628 | dns_backend_available = (relations 629 | .endpoint_from_flag('dns-backend.available')) 630 | if not (dns_backend_available or hookenv.config('dns-slaves')): 631 | return 'blocked', ('Need either a dns-backend relation or ' 632 | 'config(dns-slaves) or both.') 633 | return None, None 634 | 635 | def pool_manager_cache_sync_done(self): 636 | return hookenv.leader_get(attribute='pool-manager-cache-sync-done') 637 | 638 | def pool_manager_cache_sync(self): 639 | if not self.pool_manager_cache_sync_done() and hookenv.is_leader(): 640 | sync_cmd = "designate-manage pool-manager-cache sync" 641 | subprocess.check_call(sync_cmd.split(), timeout=60) 642 | hookenv.leader_set({'pool-manager-cache-sync-done': True}) 643 | self.restart_all() 644 | 645 | def render_nrpe(self): 646 | """Configure Nagios NRPE checks.""" 647 | hostname = nrpe.get_nagios_hostname() 648 | current_unit = nrpe.get_nagios_unit_name() 649 | charm_nrpe = nrpe.NRPE(hostname=hostname) 650 | nrpe.add_init_service_checks( 651 | charm_nrpe, self.services, current_unit) 652 | charm_nrpe.write() 653 | # Remove service checks for which services are no longer needed 654 | nrpe.remove_deprecated_check(charm_nrpe, self.deprecated_services) 655 | 656 | def add_nrpe_nameserver_checks(self): 657 | """Add NRPE service checks for upstream nameservers.""" 658 | config = hookenv.config() 659 | hostname = nrpe.get_nagios_hostname() 660 | charm_nrpe = nrpe.NRPE(hostname=hostname) 661 | if (config['nrpe-nameserver-check-host'] and 662 | 'nameservers' in config): 663 | nameservers = config['nameservers'].split() 664 | for nameserver in nameservers: 665 | if nameserver[-1] == '.': 666 | nameserver = nameserver[:-1] 667 | charm_nrpe.add_check( 668 | "nameserver-{}".format(nameserver), 669 | 'Check the upstream DNS server.', 670 | "check_dns -H {0} -s {1}".format( 671 | config['nrpe-nameserver-check-host'], 672 | nameserver), 673 | ) 674 | charm_nrpe.write() 675 | 676 | def remove_nrpe_nameserver_checks(self): 677 | """Remove NRPE service checks for previous nameservers.""" 678 | config = hookenv.config() 679 | hostname = nrpe.get_nagios_hostname() 680 | charm_nrpe = nrpe.NRPE(hostname=hostname) 681 | 682 | if (config.changed('nameservers') or 683 | config.changed('nrpe-nameserver-check-host')): 684 | for nameserver in config.previous('nameservers').split(): 685 | if nameserver[-1] == '.': 686 | nameserver = nameserver[:-1] 687 | charm_nrpe.remove_check( 688 | shortname="nameserver-{}".format(nameserver) 689 | ) 690 | charm_nrpe.write() 691 | 692 | 693 | class DesignateCharmQueens(DesignateCharm): 694 | 695 | # This charms support Queens and onward 696 | release = 'queens' 697 | 698 | services = ['designate-mdns', 'designate-zone-manager', 699 | 'designate-agent', 'designate-pool-manager', 700 | 'designate-central', 'designate-sink', 701 | 'designate-api'] 702 | 703 | restart_map = { 704 | '/etc/default/openstack': services, 705 | '/etc/designate/designate.conf': services, 706 | '/etc/designate/rndc.key': services, 707 | '/etc/designate/pools.yaml': [''], 708 | RC_FILE: [''], 709 | } 710 | 711 | def custom_assess_status_check(self): 712 | if not hookenv.config('nameservers'): 713 | return 'blocked', ('nameservers must be set') 714 | invalid_dns = self.options.invalid_pool_config() 715 | if invalid_dns: 716 | return 'blocked', invalid_dns 717 | dns_backend_available = (relations 718 | .endpoint_from_flag('dns-backend.available')) 719 | if not (dns_backend_available or hookenv.config('dns-slaves')): 720 | return 'blocked', ('Need either a dns-backend relation or ' 721 | 'config(dns-slaves) or both.') 722 | return None, None 723 | 724 | def run_upgrade(self, interfaces_list=None): 725 | """Upgrade OpenStack if an upgrade is available and action-managed 726 | upgrades is not enabled. 727 | :param interfaces_list: List of instances of interface classes 728 | :returns: None 729 | """ 730 | super(DesignateCharmQueens, self).run_upgrade( 731 | interfaces_list=interfaces_list) 732 | memcached = relations.endpoint_from_flag( 733 | 'coordinator-memcached.available') 734 | memcached.request_restart() 735 | 736 | 737 | # Inheriting from DesignateCharmQueens allows to keep 738 | # enforcing nameservers' assignment while changing 739 | # appropriate packages and services 740 | class DesignateCharmRocky(DesignateCharmQueens): 741 | 742 | release = 'rocky' 743 | packages = ['designate-agent', 'designate-api', 'designate-central', 744 | 'designate-common', 'designate-mdns', 745 | 'designate-worker', 'designate-sink', 746 | 'designate-producer', 'bind9utils', 747 | 'python3-designate', 748 | 'python-apt'] 749 | 750 | services = ['designate-mdns', 'designate-producer', 751 | 'designate-agent', 'designate-worker', 752 | 'designate-central', 'designate-sink', 753 | 'designate-api'] 754 | 755 | restart_map = { 756 | '/etc/default/openstack': services, 757 | '/etc/designate/designate.conf': services, 758 | '/etc/designate/rndc.key': services, 759 | '/etc/designate/pools.yaml': [''], 760 | RC_FILE: [''], 761 | } 762 | 763 | purge_packages = [ 764 | 'python-designate', 765 | 'python-memcache', 766 | 'designate-zone-manager', 767 | 'designate-pool-manager', 768 | ] 769 | 770 | python_version = 3 771 | 772 | def pool_manager_cache_sync(self): 773 | # NOTE(jamespage): 774 | # As the pool manager is no longer in use don't actually 775 | # sync it - just set the done flag and move on. 776 | if not self.pool_manager_cache_sync_done() and hookenv.is_leader(): 777 | hookenv.leader_set({'pool-manager-cache-sync-done': True}) 778 | 779 | 780 | class DesignateCharmVictoria(DesignateCharmRocky): 781 | 782 | release = 'victoria' 783 | packages = ['designate-agent', 'designate-api', 'designate-central', 784 | 'designate-common', 'designate-mdns', 785 | 'designate-worker', 'designate-sink', 786 | 'designate-producer', 'bind9utils', 787 | 'python3-designate', 788 | 'python3-apt'] 789 | 790 | 791 | class DesignateCharmCaracal(DesignateCharmRocky): 792 | 793 | release = 'caracal' 794 | 795 | packages = ['designate-api', 'designate-central', 796 | 'designate-common', 'designate-mdns', 797 | 'designate-worker', 'designate-sink', 798 | 'designate-producer', 'bind9utils', 799 | 'python3-designate', 800 | 'python3-apt'] 801 | 802 | purge_packages = [ 803 | 'python-designate', 804 | 'python-memcache', 805 | 'designate-agent', 806 | 'designate-zone-manager', 807 | 'designate-pool-manager', 808 | ] 809 | 810 | services = ['designate-mdns', 'designate-producer', 811 | 'designate-worker', 812 | 'designate-central', 'designate-sink', 813 | 'designate-api'] 814 | 815 | deprecated_services = ["designate-agent"] 816 | --------------------------------------------------------------------------------