Add a designate V2 API dns driver

Change-Id: Iafb36333a37146787c57eded139b4c2e071d69b0
This commit is contained in:
Sam Morrison 2019-06-12 08:50:53 +10:00 committed by Lingxian Kong
parent 7b3483723a
commit 62a2385790
4 changed files with 119 additions and 13 deletions

View File

@ -0,0 +1,5 @@
---
features:
- |
Added support for designate v2 api with a new dns driver. To use this driver
set dns_driver = trove.dns.designate.driver.DesignateDriverV2

View File

@ -144,6 +144,10 @@ common_opts = [
help='Region name for DNSaaS.'),
cfg.URIOpt('dns_auth_url', default="http://0.0.0.0",
help='Authentication URL for DNSaaS.'),
cfg.StrOpt('dns_user_domain_id', default="default",
help='Keystone user domain ID used for auth'),
cfg.StrOpt('dns_project_domain_id', default="default",
help='Keystone project domain ID used for auth'),
cfg.StrOpt('dns_domain_name', default="",
help='Domain name used for adding DNS entries.'),
cfg.StrOpt('dns_username', default="", secret=True,

View File

@ -20,8 +20,10 @@ Dns Driver that uses Designate DNSaaS.
import base64
import hashlib
from designateclient.v1 import Client
from designateclient import client
from designateclient.v1.records import Record
from keystoneauth1 import loading
from keystoneauth1 import session
from oslo_log import log as logging
from oslo_utils import encodeutils
import six
@ -44,14 +46,16 @@ DNS_PASSKEY = CONF.dns_passkey
DNS_TTL = CONF.dns_ttl
DNS_DOMAIN_ID = CONF.dns_domain_id
DNS_DOMAIN_NAME = CONF.dns_domain_name
DNS_USER_DOMAIN_ID = CONF.dns_user_domain_id
DNS_PROJECT_DOMAIN_ID = CONF.dns_project_domain_id
LOG = logging.getLogger(__name__)
class DesignateObjectConverter(object):
def domain_to_zone(self, domain):
@staticmethod
def domain_to_zone(domain):
return DesignateDnsZone(id=domain.id, name=domain.name)
def record_to_entry(self, record, dns_zone):
@ -60,22 +64,23 @@ class DesignateObjectConverter(object):
priority=record.priority, dns_zone=dns_zone)
def create_designate_client():
def create_designate_client(api_version='2'):
"""Creates a Designate DNSaaS client."""
client = Client(auth_url=DNS_AUTH_URL,
username=DNS_USERNAME,
password=DNS_PASSKEY,
tenant_id=DNS_TENANT_ID,
endpoint=DNS_ENDPOINT_URL,
service_type=DNS_SERVICE_TYPE,
region_name=DNS_REGION)
return client
loader = loading.get_plugin_loader('password')
auth = loader.load_from_options(auth_url=DNS_AUTH_URL,
username=DNS_USERNAME,
password=DNS_PASSKEY,
project_id=DNS_TENANT_ID,
user_domain_id=DNS_USER_DOMAIN_ID,
project_domain_id=DNS_PROJECT_DOMAIN_ID)
sesh = session.Session(auth=auth)
return client.Client(api_version, session=sesh)
class DesignateDriver(driver.DnsDriver):
def __init__(self):
self.dns_client = create_designate_client()
self.dns_client = create_designate_client(api_version='1')
self.converter = DesignateObjectConverter()
self.default_dns_zone = DesignateDnsZone(id=DNS_DOMAIN_ID,
name=DNS_DOMAIN_NAME)
@ -140,6 +145,46 @@ class DesignateDriver(driver.DnsDriver):
return self.dns_client.records.list(dns_zone.id)
class DesignateDriverV2(driver.DnsDriver):
def __init__(self):
self.dns_client = create_designate_client()
self.default_dns_zone = DesignateDnsZone(id=DNS_DOMAIN_ID,
name=DNS_DOMAIN_NAME)
def create_entry(self, entry, content):
"""Creates the entry in the driver at the given dns zone."""
dns_zone = entry.dns_zone or self.default_dns_zone
if not dns_zone.id:
raise TypeError(_("The entry's dns_zone must have an ID "
"specified."))
name = entry.name
LOG.debug("Creating DNS entry %s.", name)
client = self.dns_client
# Record name has to end with a '.' by dns standard
client.recordsets.create(DNS_DOMAIN_ID, entry.name + '.', entry.type,
records=[content])
def delete_entry(self, name, type, dns_zone=None):
"""Deletes an entry with the given name and type from a dns zone."""
dns_zone = dns_zone or self.default_dns_zone
records = self._get_records(dns_zone)
matching_record = [rec for rec in records
if rec['name'] == name + '.'
and rec['type'] == type]
if not matching_record:
raise exception.DnsRecordNotFound(name)
LOG.debug("Deleting DNS entry %s.", name)
self.dns_client.recordsets.delete(dns_zone.id,
matching_record[0]['id'])
def _get_records(self, dns_zone):
dns_zone = dns_zone or self.default_dns_zone
if not dns_zone:
raise TypeError(_('DNS domain is must be specified'))
return self.dns_client.recordsets.list(dns_zone.id)
class DesignateInstanceEntryFactory(driver.DnsInstanceEntryFactory):
"""Defines how instance DNS entries are created for instances."""

View File

@ -20,7 +20,9 @@ from mock import MagicMock
from mock import patch
import six
from trove.common import exception
from trove.dns.designate import driver
from trove.dns import driver as base_driver
from trove.tests.unittests import trove_testtools
@ -173,6 +175,56 @@ class DesignateDriverTest(trove_testtools.TestCase):
self.assertEqual(expected.id, actual.id)
class DesignateDriverV2Test(trove_testtools.TestCase):
def setUp(self):
super(DesignateDriverV2Test, self).setUp()
self.records = [dict(name='record1.', type='A', data='10.0.0.1',
ttl=3600, priority=1,
id='11111111-1111-1111-1111-111111111111'),
dict(name='record2.', type='CNAME', data='10.0.0.2',
ttl=1800, priority=2,
id='22222222-2222-2222-2222-222222222222'),
dict(name='record3.', type='A', data='10.0.0.3',
ttl=3600, priority=1,
id='3333333-3333-3333-3333-333333333333')]
self.mock_client = MagicMock()
self.create_des_client_patch = patch.object(
driver, 'create_designate_client', MagicMock(
return_value=self.mock_client))
self.create_des_client_mock = self.create_des_client_patch.start()
self.addCleanup(self.create_des_client_patch.stop)
def test_create_entry(self):
dns_driver = driver.DesignateDriverV2()
zone = driver.DesignateDnsZone(
id='22222222-2222-2222-2222-222222222222', name='www.trove.com')
entry = base_driver.DnsEntry(name='www.example.com', content='None',
type='A', ttl=3600, priority=None,
dns_zone=zone)
dns_driver.create_entry(entry, '1.2.3.4')
self.mock_client.recordsets.create.assert_called_once_with(
driver.DNS_DOMAIN_ID, entry.name + '.', entry.type,
records=['1.2.3.4'])
def test_delete_entry(self):
with patch.object(driver.DesignateDriverV2, '_get_records',
MagicMock(return_value=self.records)):
dns_driver = driver.DesignateDriverV2()
dns_driver.delete_entry('record1', 'A')
self.mock_client.recordsets.delete(driver.DNS_DOMAIN_ID)
def test_delete_no_entry(self):
with patch.object(driver.DesignateDriverV2, '_get_records',
MagicMock(return_value=self.records)):
dns_driver = driver.DesignateDriverV2()
self.assertRaises(exception.DnsRecordNotFound,
dns_driver.delete_entry,
'nothere', 'A')
self.mock_client.recordsets.assert_not_called()
class DesignateInstanceEntryFactoryTest(trove_testtools.TestCase):
def setUp(self):