Merge "Add support for Designate zones"
This commit is contained in:
commit
898bc436e8
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Add support for Designate zones resources, with the
|
||||
usual methods (search/list/get/create/update/delete).
|
@ -19,5 +19,6 @@ python-troveclient>=1.2.0
|
||||
python-ironicclient>=0.10.0
|
||||
python-swiftclient>=2.5.0
|
||||
python-heatclient>=0.3.0
|
||||
python-designateclient>=2.1.0
|
||||
|
||||
dogpile.cache>=0.5.3
|
||||
|
@ -772,3 +772,23 @@ class StackDelete(task_manager.Task):
|
||||
class StackGet(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.heat_client.stacks.get(**self.args)
|
||||
|
||||
|
||||
class ZoneList(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.designate_client.zones.list()
|
||||
|
||||
|
||||
class ZoneCreate(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.designate_client.zones.create(**self.args)
|
||||
|
||||
|
||||
class ZoneUpdate(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.designate_client.zones.update(**self.args)
|
||||
|
||||
|
||||
class ZoneDelete(task_manager.Task):
|
||||
def main(self, client):
|
||||
return client.designate_client.zones.delete(**self.args)
|
||||
|
@ -42,6 +42,7 @@ import swiftclient.client
|
||||
import swiftclient.service
|
||||
import swiftclient.exceptions as swift_exceptions
|
||||
import troveclient.client
|
||||
import designateclient.client
|
||||
|
||||
from shade.exc import * # noqa
|
||||
from shade import _log
|
||||
@ -260,6 +261,7 @@ class OpenStackCloud(object):
|
||||
self._swift_client_lock = threading.Lock()
|
||||
self._swift_service_lock = threading.Lock()
|
||||
self._trove_client = None
|
||||
self._designate_client = None
|
||||
|
||||
self._raw_clients = {}
|
||||
|
||||
@ -848,6 +850,13 @@ class OpenStackCloud(object):
|
||||
'network', neutronclient.neutron.client.Client)
|
||||
return self._neutron_client
|
||||
|
||||
@property
|
||||
def designate_client(self):
|
||||
if self._designate_client is None:
|
||||
self._designate_client = self._get_client(
|
||||
'dns', designateclient.client.Client)
|
||||
return self._designate_client
|
||||
|
||||
def create_stack(
|
||||
self, name,
|
||||
template_file=None, template_url=None,
|
||||
@ -5093,3 +5102,113 @@ class OpenStackCloud(object):
|
||||
raise OpenStackCloudUnavailableFeature(
|
||||
"Unavailable feature: security groups"
|
||||
)
|
||||
|
||||
def list_zones(self):
|
||||
"""List all available zones.
|
||||
|
||||
:returns: A list of zones dicts.
|
||||
|
||||
"""
|
||||
with _utils.shade_exceptions("Error fetching zones list"):
|
||||
return self.manager.submitTask(_tasks.ZoneList())
|
||||
|
||||
def get_zone(self, name_or_id, filters=None):
|
||||
"""Get a zone by name or ID.
|
||||
|
||||
:param name_or_id: Name or ID of the zone
|
||||
:param dict filters:
|
||||
A dictionary of meta data to use for further filtering
|
||||
|
||||
:returns: A zone dict or None if no matching zone is
|
||||
found.
|
||||
|
||||
"""
|
||||
return _utils._get_entity(self.search_zones, name_or_id, filters)
|
||||
|
||||
def search_zones(self, name_or_id=None, filters=None):
|
||||
zones = self.list_zones()
|
||||
return _utils._filter_list(zones, name_or_id, filters)
|
||||
|
||||
def create_zone(self, name, zone_type=None, email=None, description=None,
|
||||
ttl=None, masters=None):
|
||||
"""Create a new zone.
|
||||
|
||||
:param name: Name of the zone being created.
|
||||
:param zone_type: Type of the zone (primary/secondary)
|
||||
:param email: Email of the zone owner (only
|
||||
applies if zone_type is primary)
|
||||
:param description: Description of the zone
|
||||
:param ttl: TTL (Time to live) value in seconds
|
||||
:param masters: Master nameservers (only applies
|
||||
if zone_type is secondary)
|
||||
|
||||
:returns: a dict representing the created zone.
|
||||
|
||||
:raises: OpenStackCloudException on operation error.
|
||||
"""
|
||||
|
||||
# We capitalize in case the user passes time in lowercase, as
|
||||
# designate call expects PRIMARY/SECONDARY
|
||||
if zone_type is not None:
|
||||
zone_type = zone_type.upper()
|
||||
if zone_type not in ('PRIMARY', 'SECONDARY'):
|
||||
raise OpenStackCloudException(
|
||||
"Invalid type %s, valid choices are PRIMARY or SECONDARY" %
|
||||
zone_type)
|
||||
|
||||
with _utils.shade_exceptions("Unable to create zone {name}".format(
|
||||
name=name)):
|
||||
return self.manager.submitTask(_tasks.ZoneCreate(
|
||||
name=name, type_=zone_type, email=email,
|
||||
description=description, ttl=ttl, masters=masters))
|
||||
|
||||
@_utils.valid_kwargs('email', 'description', 'ttl', 'masters')
|
||||
def update_zone(self, name_or_id, **kwargs):
|
||||
"""Update a zone.
|
||||
|
||||
:param name_or_id: Name or ID of the zone being updated.
|
||||
:param email: Email of the zone owner (only
|
||||
applies if zone_type is primary)
|
||||
:param description: Description of the zone
|
||||
:param ttl: TTL (Time to live) value in seconds
|
||||
:param masters: Master nameservers (only applies
|
||||
if zone_type is secondary)
|
||||
|
||||
:returns: a dict representing the updated zone.
|
||||
|
||||
:raises: OpenStackCloudException on operation error.
|
||||
"""
|
||||
zone = self.get_zone(name_or_id)
|
||||
if not zone:
|
||||
raise OpenStackCloudException(
|
||||
"Zone %s not found." % name_or_id)
|
||||
|
||||
with _utils.shade_exceptions(
|
||||
"Error updating zone {0}".format(name_or_id)):
|
||||
new_zone = self.manager.submitTask(
|
||||
_tasks.ZoneUpdate(
|
||||
zone=zone['id'], values=kwargs))
|
||||
|
||||
return new_zone
|
||||
|
||||
def delete_zone(self, name_or_id):
|
||||
"""Delete a zone.
|
||||
|
||||
:param name_or_id: Name or ID of the zone being deleted.
|
||||
|
||||
:returns: True if delete succeeded, False otherwise.
|
||||
|
||||
:raises: OpenStackCloudException on operation error.
|
||||
"""
|
||||
|
||||
zone = self.get_zone(name_or_id)
|
||||
if zone is None:
|
||||
self.log.debug("Zone %s not found for deleting" % name_or_id)
|
||||
return False
|
||||
|
||||
with _utils.shade_exceptions(
|
||||
"Error deleting zone {0}".format(name_or_id)):
|
||||
self.manager.submitTask(
|
||||
_tasks.ZoneDelete(zone=zone['id']))
|
||||
|
||||
return True
|
||||
|
@ -248,3 +248,15 @@ class FakeStack(object):
|
||||
self.stack_name = name
|
||||
self.stack_description = description
|
||||
self.stack_status = status
|
||||
|
||||
|
||||
class FakeZone(object):
|
||||
def __init__(self, id, name, type_, email, description,
|
||||
ttl, masters):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.type_ = type_
|
||||
self.email = email
|
||||
self.description = description
|
||||
self.ttl = ttl
|
||||
self.masters = masters
|
||||
|
86
shade/tests/functional/test_zone.py
Normal file
86
shade/tests/functional/test_zone.py
Normal file
@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
test_zone
|
||||
----------------------------------
|
||||
|
||||
Functional tests for `shade` zone methods.
|
||||
"""
|
||||
|
||||
from testtools import content
|
||||
|
||||
from shade.tests.functional import base
|
||||
|
||||
|
||||
class TestZone(base.BaseFunctionalTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestZone, self).setUp()
|
||||
if not self.demo_cloud.has_service('dns'):
|
||||
self.skipTest('dns service not supported by cloud')
|
||||
|
||||
def test_zones(self):
|
||||
'''Test DNS zones functionality'''
|
||||
name = 'example.net.'
|
||||
zone_type = 'primary'
|
||||
email = 'test@example.net'
|
||||
description = 'Test zone'
|
||||
ttl = 3600
|
||||
masters = None
|
||||
|
||||
self.addDetail('zone', content.text_content(name))
|
||||
self.addCleanup(self.cleanup, name)
|
||||
|
||||
# Test we can create a zone and we get it returned
|
||||
zone = self.demo_cloud.create_zone(
|
||||
name=name, zone_type=zone_type, email=email,
|
||||
description=description, ttl=ttl,
|
||||
masters=masters)
|
||||
self.assertEquals(zone['name'], name)
|
||||
self.assertEquals(zone['type'], zone_type.upper())
|
||||
self.assertEquals(zone['email'], email)
|
||||
self.assertEquals(zone['description'], description)
|
||||
self.assertEquals(zone['ttl'], ttl)
|
||||
self.assertEquals(zone['masters'], [])
|
||||
|
||||
# Test that we can list zones
|
||||
zones = self.demo_cloud.list_zones()
|
||||
self.assertIsNotNone(zones)
|
||||
|
||||
# Test we get the same zone with the get_zone method
|
||||
zone_get = self.demo_cloud.get_zone(zone['id'])
|
||||
self.assertEquals(zone_get['id'], zone['id'])
|
||||
|
||||
# Test the get method also works by name
|
||||
zone_get = self.demo_cloud.get_zone(name)
|
||||
self.assertEquals(zone_get['name'], zone['name'])
|
||||
|
||||
# Test we can update a field on the zone and only that field
|
||||
# is updated
|
||||
zone_update = self.demo_cloud.update_zone(zone['id'], ttl=7200)
|
||||
self.assertEquals(zone_update['id'], zone['id'])
|
||||
self.assertEquals(zone_update['name'], zone['name'])
|
||||
self.assertEquals(zone_update['type'], zone['type'])
|
||||
self.assertEquals(zone_update['email'], zone['email'])
|
||||
self.assertEquals(zone_update['description'], zone['description'])
|
||||
self.assertEquals(zone_update['ttl'], 7200)
|
||||
self.assertEquals(zone_update['masters'], zone['masters'])
|
||||
|
||||
# Test we can delete and get True returned
|
||||
zone_delete = self.demo_cloud.delete_zone(zone['id'])
|
||||
self.assertTrue(zone_delete)
|
||||
|
||||
def cleanup(self, name):
|
||||
self.demo_cloud.delete_zone(name)
|
97
shade/tests/unit/test_zone.py
Normal file
97
shade/tests/unit/test_zone.py
Normal file
@ -0,0 +1,97 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
import shade
|
||||
from shade.tests.unit import base
|
||||
from shade.tests import fakes
|
||||
|
||||
|
||||
zone_obj = fakes.FakeZone(
|
||||
id='1',
|
||||
name='example.net.',
|
||||
type_='PRIMARY',
|
||||
email='test@example.net',
|
||||
description='Example zone',
|
||||
ttl=3600,
|
||||
masters=None
|
||||
)
|
||||
|
||||
|
||||
class TestZone(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestZone, self).setUp()
|
||||
self.cloud = shade.openstack_cloud(validate=False)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'designate_client')
|
||||
def test_create_zone(self, mock_designate):
|
||||
self.cloud.create_zone(name=zone_obj.name, zone_type=zone_obj.type_,
|
||||
email=zone_obj.email,
|
||||
description=zone_obj.description,
|
||||
ttl=zone_obj.ttl, masters=zone_obj.masters)
|
||||
mock_designate.zones.create.assert_called_once_with(
|
||||
name=zone_obj.name, type_=zone_obj.type_.upper(),
|
||||
email=zone_obj.email, description=zone_obj.description,
|
||||
ttl=zone_obj.ttl, masters=zone_obj.masters
|
||||
)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'designate_client')
|
||||
def test_create_zone_exception(self, mock_designate):
|
||||
mock_designate.zones.create.side_effect = Exception()
|
||||
with testtools.ExpectedException(
|
||||
shade.OpenStackCloudException,
|
||||
"Unable to create zone example.net."
|
||||
):
|
||||
self.cloud.create_zone('example.net.')
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'designate_client')
|
||||
def test_update_zone(self, mock_designate):
|
||||
new_ttl = 7200
|
||||
mock_designate.zones.list.return_value = [zone_obj]
|
||||
self.cloud.update_zone('1', ttl=new_ttl)
|
||||
mock_designate.zones.update.assert_called_once_with(
|
||||
zone='1', values={'ttl': new_ttl}
|
||||
)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'designate_client')
|
||||
def test_delete_zone(self, mock_designate):
|
||||
mock_designate.zones.list.return_value = [zone_obj]
|
||||
self.cloud.delete_zone('1')
|
||||
mock_designate.zones.delete.assert_called_once_with(
|
||||
zone='1'
|
||||
)
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'designate_client')
|
||||
def test_get_zone_by_id(self, mock_designate):
|
||||
mock_designate.zones.list.return_value = [zone_obj]
|
||||
zone = self.cloud.get_zone('1')
|
||||
self.assertTrue(mock_designate.zones.list.called)
|
||||
self.assertEqual(zone['id'], '1')
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'designate_client')
|
||||
def test_get_zone_by_name(self, mock_designate):
|
||||
mock_designate.zones.list.return_value = [zone_obj]
|
||||
zone = self.cloud.get_zone('example.net.')
|
||||
self.assertTrue(mock_designate.zones.list.called)
|
||||
self.assertEqual(zone['name'], 'example.net.')
|
||||
|
||||
@mock.patch.object(shade.OpenStackCloud, 'designate_client')
|
||||
def test_get_zone_not_found_returns_false(self, mock_designate):
|
||||
mock_designate.zones.list.return_value = []
|
||||
zone = self.cloud.get_zone('nonexistingzone.net.')
|
||||
self.assertFalse(zone)
|
Loading…
Reference in New Issue
Block a user