Commit 1d0934d9 authored by Thomas Gambier's avatar Thomas Gambier 🚴🏼

software/powerdns: add tests

See merge request nexedi/slapos!856
parents cb071230 b15ec0ec
Pipeline #12417 failed with stage
in 0 seconds
...@@ -18,15 +18,15 @@ md5sum = fddea033e1aa9d6147a1a47bd7cc4b62 ...@@ -18,15 +18,15 @@ md5sum = fddea033e1aa9d6147a1a47bd7cc4b62
[template-powerdns] [template-powerdns]
filename = instance-powerdns.cfg filename = instance-powerdns.cfg
md5sum = 9cd4e436fa432f37b9f8f4de8350581b md5sum = 0920200cb05a68b1b4a161a927d9488f
[template-pdns-configuration] [template-pdns-configuration]
_update_hash_filename_ = template/pdns.conf.jinja2 _update_hash_filename_ = template/pdns.conf.jinja2
md5sum = e45d72de87b4adb89c195ba463be4077 md5sum = 20c37ea06a8fa405bc02470d5115fd11
[template-dns-replicate] [template-dns-replicate]
_update_hash_filename_ = instance-powerdns-replicate.cfg.jinja2 _update_hash_filename_ = instance-powerdns-replicate.cfg.jinja2
md5sum = a23e241a236f90ae1afbb5bd5ba0b32d md5sum = c2bd424f588ad57d37f4cf1329734fb6
[iso-list] [iso-list]
_update_hash_filename_ = template/zz.countries.nexedi.dk.rbldnsd _update_hash_filename_ = template/zz.countries.nexedi.dk.rbldnsd
......
...@@ -43,6 +43,7 @@ context = ...@@ -43,6 +43,7 @@ context =
{% set dns_name = 'ns%s' % i -%} {% set dns_name = 'ns%s' % i -%}
{% set dns_domain = dns_name_template_string % i %} {% set dns_domain = dns_name_template_string % i %}
{% set request_section_title = 'request-%s' % dns_name -%} {% set request_section_title = 'request-%s' % dns_name -%}
{% set promise_section_title = 'promise-listen-port-%s' % dns_name -%}
{% set sla_key = "-sla-%s-" % i -%} {% set sla_key = "-sla-%s-" % i -%}
{% set sla_key_length = sla_key | length %} {% set sla_key_length = sla_key | length %}
{% set sla_parameters = [] %} {% set sla_parameters = [] %}
...@@ -55,6 +56,7 @@ context = ...@@ -55,6 +56,7 @@ context =
{% do dns_domain_list.append(dns_domain) -%} {% do dns_domain_list.append(dns_domain) -%}
{% do dns_section_list.append(request_section_title) -%} {% do dns_section_list.append(request_section_title) -%}
{% do part_list.append(request_section_title) -%} {% do part_list.append(request_section_title) -%}
{% do part_list.append(promise_section_title) -%}
[{{request_section_title}}] [{{request_section_title}}]
<= replicate <= replicate
...@@ -69,6 +71,15 @@ config-soa = {{ "%s,%s" % (dns_domain, server_admin) }} ...@@ -69,6 +71,15 @@ config-soa = {{ "%s,%s" % (dns_domain, server_admin) }}
sla-{{ parameter }} = {{ slapparameter_dict.pop( sla_key + parameter ) }} sla-{{ parameter }} = {{ slapparameter_dict.pop( sla_key + parameter ) }}
{% endfor -%} {% endfor -%}
[{{promise_section_title}}]
<= monitor-promise-base
module = check_port_listening
name = pdns-port-listening.py
{% set ipv6 = '${' ~ request_section_title ~ ':connection-powerdns-ipv6}' -%}
config-hostname = {{ipv6}}
{% set port = '${' ~ request_section_title ~ ':connection-powerdns-port}' -%}
config-port = {{port}}
{% do monitor_url_list.append('${' ~ request_section_title ~ ':connection-monitor-base-url}') -%} {% do monitor_url_list.append('${' ~ request_section_title ~ ':connection-monitor-base-url}') -%}
{% endfor -%} {% endfor -%}
...@@ -101,7 +112,7 @@ software-url = {{ slapparameter_dict.pop(dns_software_url_key) }} ...@@ -101,7 +112,7 @@ software-url = {{ slapparameter_dict.pop(dns_software_url_key) }}
software-url = ${slap-connection:software-release-url} software-url = ${slap-connection:software-release-url}
{% endif %} {% endif %}
software-type = {{dns_type}} software-type = {{dns_type}}
return = private-ipv4 public-ipv4 slave-instance-information-list monitor-base-url return = slave-instance-information-list monitor-base-url powerdns-ipv6 powerdns-port powerdns-ipv4
config-server-admin = {{ server_admin }} config-server-admin = {{ server_admin }}
config-ns-record = {{ ns_record }} config-ns-record = {{ ns_record }}
{% for parameter, value in slapparameter_dict.items() -%} {% for parameter, value in slapparameter_dict.items() -%}
...@@ -114,12 +125,17 @@ config-monitor-password = ${monitor-htpasswd:passwd} ...@@ -114,12 +125,17 @@ config-monitor-password = ${monitor-htpasswd:passwd}
[publish-information] [publish-information]
recipe = slapos.cookbook:publish recipe = slapos.cookbook:publish
domain = {{ slapparameter_dict.get('domain') }}
slave-amount = {{ slave_instance_list | length }} slave-amount = {{ slave_instance_list | length }}
ns-record = {{ ns_record }} ns-record = {{ ns_record }}
monitor-url = ${monitor-publish-parameters:monitor-url} monitor-url = ${monitor-publish-parameters:monitor-url}
monitor-user = ${monitor-publish-parameters:monitor-user} monitor-user = ${monitor-publish-parameters:monitor-user}
monitor-password = ${monitor-publish-parameters:monitor-password} monitor-password = ${monitor-publish-parameters:monitor-password}
{% for dns_name, dns_section in zip(dns_list, dns_section_list) -%}
{% set dns_ipv6 = '${' ~ dns_section ~ ':connection-powerdns-ipv6}' -%}
{% set dns_port = '${' ~ dns_section ~ ':connection-powerdns-port}' -%}
{{ dns_name }}-ipv6 = {{ dns_ipv6 }}
{{ dns_name }}-port = {{ dns_port }}
{% endfor -%}
{% set monitor_interface_url = slapparameter_dict.get('monitor-interface-url', 'https://monitor.app.officejs.com') -%} {% set monitor_interface_url = slapparameter_dict.get('monitor-interface-url', 'https://monitor.app.officejs.com') -%}
{% if monitor_interface_url -%} {% if monitor_interface_url -%}
monitor-setup-url = {{ monitor_interface_url }}/#page=settings_configurator&url=${monitor-publish-parameters:monitor-url} monitor-setup-url = {{ monitor_interface_url }}/#page=settings_configurator&url=${monitor-publish-parameters:monitor-url}
......
...@@ -55,7 +55,7 @@ context = ...@@ -55,7 +55,7 @@ context =
# #
[pdns] [pdns]
configuration = $${pdns-directory:configuration}/pdns.conf configuration = $${pdns-directory:configuration}/pdns.conf
local-ipv4 = $${instance-parameter:ipv4-random} ipv4 = $${instance-parameter:ipv4-random}
ipv6 = $${instance-parameter:ipv6-random} ipv6 = $${instance-parameter:ipv6-random}
port = 5353 port = 5353
socket-directory = $${pdns-directory:socket-directory} socket-directory = $${pdns-directory:socket-directory}
...@@ -139,7 +139,7 @@ extra-context = ...@@ -139,7 +139,7 @@ extra-context =
<= monitor-promise-base <= monitor-promise-base
module = check_port_listening module = check_port_listening
name = pdns-port-listening.py name = pdns-port-listening.py
config-hostname = $${pdns:local-ipv4} config-hostname = $${pdns:ipv4}
config-port = $${pdns:port} config-port = $${pdns:port}
[publish-connection-informations] [publish-connection-informations]
...@@ -147,6 +147,9 @@ recipe = slapos.cookbook:publish ...@@ -147,6 +147,9 @@ recipe = slapos.cookbook:publish
monitor-url = $${monitor-publish-parameters:monitor-url} monitor-url = $${monitor-publish-parameters:monitor-url}
monitor-user = $${monitor-publish-parameters:monitor-user} monitor-user = $${monitor-publish-parameters:monitor-user}
monitor-password = $${monitor-publish-parameters:monitor-password} monitor-password = $${monitor-publish-parameters:monitor-password}
powerdns-ipv4 = $${pdns:ipv4}
powerdns-ipv6 = $${pdns:ipv6}
powerdns-port = $${pdns:port}
[buildout] [buildout]
parts = parts =
......
...@@ -55,7 +55,6 @@ PyRSS2Gen = 1.1 ...@@ -55,7 +55,6 @@ PyRSS2Gen = 1.1
cns.recipe.symlink = 0.2.3 cns.recipe.symlink = 0.2.3
plone.recipe.command = 1.1 plone.recipe.command = 1.1
slapos.recipe.template = 4.4 slapos.recipe.template = 4.4
dnspython = 1.15.0
passlib = 1.7.1 passlib = 1.7.1
GitPython = 2.1.11 GitPython = 2.1.11
lockfile = 0.12.2 lockfile = 0.12.2
......
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Configure ip/port binding # Configure ip/port binding
local-address={{ pdns.get('local-ipv4') }} local-address={{ pdns.get('ipv4') }}
local-ipv6={{ pdns.get('ipv6') }} local-ipv6={{ pdns.get('ipv6') }}
local-port={{ pdns.get('port') }} local-port={{ pdns.get('port') }}
...@@ -10,7 +10,7 @@ socket-dir={{ pdns.get('socket-directory') }} ...@@ -10,7 +10,7 @@ socket-dir={{ pdns.get('socket-directory') }}
# Monitoring # Monitoring
webserver=yes webserver=yes
webserver-address={{ pdns.get('local-ipv4') }} webserver-address={{ pdns.get('ipv4') }}
webserver-port={{ pdns.get('webserver-port') }} webserver-port={{ pdns.get('webserver-port') }}
# These totally disable query+packet caching for all zones. This is necessary # These totally disable query+packet caching for all zones. This is necessary
......
...@@ -47,6 +47,7 @@ setup(name=name, ...@@ -47,6 +47,7 @@ setup(name=name,
'erp5.util', 'erp5.util',
'supervisor', 'supervisor',
'psutil', 'psutil',
'dnspython',
], ],
zip_safe=True, zip_safe=True,
test_suite='test', test_suite='test',
......
...@@ -25,7 +25,12 @@ ...@@ -25,7 +25,12 @@
# #
############################################################################## ##############################################################################
import dns.edns
import dns.message
import dns.query
import http.client
import os import os
import requests
from slapos.recipe.librecipe import generateHashFromFiles from slapos.recipe.librecipe import generateHashFromFiles
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
...@@ -35,10 +40,64 @@ setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass( ...@@ -35,10 +40,64 @@ setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass(
os.path.abspath( os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', 'software.cfg'))) os.path.join(os.path.dirname(__file__), '..', 'software.cfg')))
DNS_PORT = 5353
AFRICAN_SUBNET = '41.0.0.0'
CHINA_TELECOM_SUBNET = '1.0.32.0'
CHINA_UNICOM_SUBNET = '116.181.0.0'
CHINA_MOBILE_SUBNET = '112.21.42.5'
EAST_ASIAN_SUBNET = '1.11.0.0'
EUROPEAN_SUBNET = '5.42.160.0'
HONG_KONG_SUBNET = '1.32.192.0'
JAPANESE_SUBNET = '1.0.16.0'
NORTH_AMERICAN_SUBNET = '3.0.0.0'
OCEANIAN_SUBNET = '1.120.0.0'
SOUTH_AMERICAN_SUBNET = '45.70.188.0'
WEST_ASIAN_SUBNET = '46.70.0.0'
class PowerDNSTestCase(SlapOSInstanceTestCase): class PowerDNSTestCase(SlapOSInstanceTestCase):
# power dns uses sockets and need shorter paths on test nodes. # power dns uses sockets and need shorter paths on test nodes.
__partition_reference__ = 'pdns' __partition_reference__ = 'pdns'
default_zone = 'domain.com'
# focus to test connexion parameters only depending on PowerDNS
def getPowerDNSParameterDict(self, parameter_dict):
new_parameter_dict = {}
for key, value in parameter_dict.items():
if key in [
'ns-record',
'ns1-port',
'ns1-ipv6',
'slave-amount',
]:
new_parameter_dict[key] = value
return new_parameter_dict
def getPowerDNSConnexionParameterDict(self):
return self.getPowerDNSParameterDict(
self.requestDefaultInstance().getConnectionParameterDict()
)
def _test_parameter_dict(self, zone=None, dns_quantity=1, slave_amount=0):
if zone is None:
zone = self.default_zone
parameter_dict = self.getPowerDNSConnexionParameterDict()
expected_dict = {
'ns-record': '',
}
ns_record = ''
for replicate_nb in range(1, dns_quantity + 1):
prefix = 'ns%s' % replicate_nb
ns_record += prefix + '.%s' % zone
expected_dict[prefix + '-port'] = str(DNS_PORT)
expected_dict[prefix + '-ipv6'] = self._ipv6_address
expected_dict['ns-record'] = ns_record
expected_dict['slave-amount'] = str(slave_amount)
self.assertEqual(expected_dict, parameter_dict)
class ServicesTestCase(PowerDNSTestCase): class ServicesTestCase(PowerDNSTestCase):
...@@ -63,3 +122,194 @@ class ServicesTestCase(PowerDNSTestCase): ...@@ -63,3 +122,194 @@ class ServicesTestCase(PowerDNSTestCase):
expected_process_name = name.format(hash=h) expected_process_name = name.format(hash=h)
self.assertIn(expected_process_name, process_names) self.assertIn(expected_process_name, process_names)
class TestMonitorAccess(PowerDNSTestCase):
def test(self):
connection_parameter_dict = self.requestDefaultInstance()\
.getConnectionParameterDict()
monitor_base_url = connection_parameter_dict.get('monitor-url')
result = requests.get(
monitor_base_url, verify=False, auth=(
connection_parameter_dict.get('monitor-user'),
connection_parameter_dict.get('monitor-password')
)
)
self.assertEqual(
http.client.OK,
result.status_code
)
class TestMasterRequest(PowerDNSTestCase):
def test(self):
self._test_parameter_dict()
class TestMasterRequestDomain(PowerDNSTestCase):
@classmethod
def getInstanceParameterDict(cls):
return {
'zone': 'toto.example.com',
}
def test(self):
self._test_parameter_dict(zone=self.getInstanceParameterDict()['zone'])
class PowerDNSSlaveTestCase(PowerDNSTestCase):
@classmethod
def requestDefaultInstance(cls):
default_instance = super(PowerDNSSlaveTestCase, cls)\
.requestDefaultInstance()
cls.requestSlaves()
return default_instance
@classmethod
def requestSlaves(cls):
software_url = cls.getSoftwareURL()
software_type = cls.getInstanceSoftwareType()
for slave_reference, partition_parameter_kw in cls\
.getSlaveParameterDictDict().items():
cls.logger.debug(
'requesting slave "%s" software:%s parameters:%s',
slave_reference, software_url, partition_parameter_kw)
cls.slap.request(
software_release=software_url,
software_type=software_type,
partition_reference=slave_reference,
partition_parameter_kw=partition_parameter_kw,
shared=True
)
@classmethod
def getSlaveConnectionParameterDictList(cls):
parameter_dict_list = []
for slave_reference, partition_parameter_kw in cls\
.getSlaveParameterDictDict().items():
parameter_dict_list.append(cls.slap.request(
software_release=cls.getSoftwareURL(),
software_type=cls.getInstanceSoftwareType(),
partition_reference=slave_reference,
partition_parameter_kw=partition_parameter_kw,
shared=True
).getConnectionParameterDict())
return parameter_dict_list
@classmethod
def getSlaveParameterDictDict(cls):
return {
'slave-pdns1': {
'record': 'test1',
'origin': 'nexedi.com',
'default': 'test1.com.',
'africa': 'test1africa.com.',
'china-telecom': 'test1china-telecom.com.',
'china-unicom': 'test1china-unicom.com.',
'china-mobile': 'test1china-mobile.com.',
'east-asia': 'test1east-asia.com.',
'europe': 'test1europe.com.',
'hong-kong': 'test1hong-kong.com.',
'japan': 'test1japan.com.',
'north-america': 'test1north-america.com.',
'oceania': 'test1oceania.com.',
'south-america': 'test1south-america.com.',
'west-asia': 'test1west-asia.com.',
},
'slave-pdns2': {
'record': 'test2',
'origin': 'nexedi.com',
'default': 'test2.com.',
'china-telecom': 'test2china-telecom.com.',
'europe': 'test2europe.com.',
'japan': 'test2japan.com.',
}
}
def dns_query(self, domain_name, subnet):
message = dns.message.make_query(domain_name, 'A')
client_subnet_option = dns.edns.ECSOption(subnet)
message.use_edns(options=[client_subnet_option])
answer = dns.query.udp(message, self._ipv6_address, port=DNS_PORT)
return answer.find_rrset(
dns.message.ANSWER,
dns.name.from_text(domain_name),
dns.rdataclass.IN,
dns.rdatatype.CNAME
).to_text().split()[-1]
def _test_dns_resolver(self, zone):
slave_parameter_dict_dict = self.getSlaveParameterDictDict()
subnet_dict = {
'africa': AFRICAN_SUBNET,
'china-telecom': CHINA_TELECOM_SUBNET,
'china-unicom': CHINA_UNICOM_SUBNET,
'china-mobile': CHINA_MOBILE_SUBNET,
'east-asia': EAST_ASIAN_SUBNET,
'europe': EUROPEAN_SUBNET,
'hong-kong': HONG_KONG_SUBNET,
'japan': JAPANESE_SUBNET,
'north-america': NORTH_AMERICAN_SUBNET,
'oceania': OCEANIAN_SUBNET,
'south-america': SOUTH_AMERICAN_SUBNET,
'west-asia': WEST_ASIAN_SUBNET,
}
default_rr_dict = {
'europe': 'eu',
'africa': 'af',
'south-america': 'sa',
'north-america': 'na',
'china-telecom': 'cn-t',
'china-unicom': 'cn-u',
'china-mobile': 'cn-m',
'japan': 'jp',
'hong-kong': 'hk',
'east-asia': 'as',
'west-asia': 'eu',
'oceania': 'oc',
}
for slave_name in slave_parameter_dict_dict:
slave_parameter_dict = slave_parameter_dict_dict[slave_name]
domain_name = '%s.%s' % (slave_parameter_dict['record'], zone)
for region in subnet_dict:
self.assertEqual(
slave_parameter_dict.pop(
region,
'%s.%s.' % (default_rr_dict[region], slave_parameter_dict['origin'])
),
self.dns_query(domain_name, subnet_dict[region])
)
def _test(self, zone=None):
if zone is None:
zone = self.default_zone
self._test_parameter_dict(
zone=zone,
slave_amount=len(self.getSlaveParameterDictDict())
)
self._test_dns_resolver(zone)
class TestSlaveRequest(PowerDNSSlaveTestCase):
def test(self):
self._test()
class TestSlaveRequestDomain(PowerDNSSlaveTestCase):
@classmethod
def getInstanceParameterDict(cls):
return {
'zone': 'toto.example.com',
}
def test(self):
self._test(zone=self.getInstanceParameterDict()['zone'])
...@@ -247,7 +247,7 @@ croniter = 0.3.25 ...@@ -247,7 +247,7 @@ croniter = 0.3.25
# Required by: # Required by:
# slapos.toolbox==0.94 # slapos.toolbox==0.94
dnspython = 1.15.0 dnspython = 1.16.0
# Required by: # Required by:
# cryptography==1.8.1 # cryptography==1.8.1
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment