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-ironicclient>=0.10.0
|
||||||
python-swiftclient>=2.5.0
|
python-swiftclient>=2.5.0
|
||||||
python-heatclient>=0.3.0
|
python-heatclient>=0.3.0
|
||||||
|
python-designateclient>=2.1.0
|
||||||
|
|
||||||
dogpile.cache>=0.5.3
|
dogpile.cache>=0.5.3
|
||||||
|
@ -772,3 +772,23 @@ class StackDelete(task_manager.Task):
|
|||||||
class StackGet(task_manager.Task):
|
class StackGet(task_manager.Task):
|
||||||
def main(self, client):
|
def main(self, client):
|
||||||
return client.heat_client.stacks.get(**self.args)
|
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.service
|
||||||
import swiftclient.exceptions as swift_exceptions
|
import swiftclient.exceptions as swift_exceptions
|
||||||
import troveclient.client
|
import troveclient.client
|
||||||
|
import designateclient.client
|
||||||
|
|
||||||
from shade.exc import * # noqa
|
from shade.exc import * # noqa
|
||||||
from shade import _log
|
from shade import _log
|
||||||
@ -260,6 +261,7 @@ class OpenStackCloud(object):
|
|||||||
self._swift_client_lock = threading.Lock()
|
self._swift_client_lock = threading.Lock()
|
||||||
self._swift_service_lock = threading.Lock()
|
self._swift_service_lock = threading.Lock()
|
||||||
self._trove_client = None
|
self._trove_client = None
|
||||||
|
self._designate_client = None
|
||||||
|
|
||||||
self._raw_clients = {}
|
self._raw_clients = {}
|
||||||
|
|
||||||
@ -848,6 +850,13 @@ class OpenStackCloud(object):
|
|||||||
'network', neutronclient.neutron.client.Client)
|
'network', neutronclient.neutron.client.Client)
|
||||||
return self._neutron_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(
|
def create_stack(
|
||||||
self, name,
|
self, name,
|
||||||
template_file=None, template_url=None,
|
template_file=None, template_url=None,
|
||||||
@ -5093,3 +5102,113 @@ class OpenStackCloud(object):
|
|||||||
raise OpenStackCloudUnavailableFeature(
|
raise OpenStackCloudUnavailableFeature(
|
||||||
"Unavailable feature: security groups"
|
"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_name = name
|
||||||
self.stack_description = description
|
self.stack_description = description
|
||||||
self.stack_status = status
|
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