Remove support for /os-floating-ip-dns REST API

Drop support for the os-floating-ip-dns API which has been deprecated
since Newton:

  Idca478c566f9a7b5b30a3172453ce7c66d9fd8f0

This API now returns a 410 response for all routes.

Unit tests are removed and the functional API sample tests are just
asserting the 410 response now.

The API sample docs are left intact since the API reference still builds
from those and can be considered more or less branchless, so people
looking at the API reference can apply it to older deployments of nova
before os-floating-ip-dns was removed.

The release note added for previous nova-network API removals is
amended to note this additional change.

Part of blueprint remove-nova-network

Change-Id: I0c4b586292814b8483226aee315f41cbefc86a1e
This commit is contained in:
Stephen Finucane 2018-07-16 13:25:50 +01:00 committed by Matt Riedemann
parent 823c4e840d
commit db294b1e33
18 changed files with 84 additions and 958 deletions

View File

@ -60,7 +60,6 @@ the `API guide <http://developer.openstack.org/api-guide/compute/index.html>`_.
.. include:: images.inc
.. include:: os-baremetal-nodes.inc
.. include:: os-tenant-network.inc
.. include:: os-floating-ip-dns.inc
.. include:: os-floating-ip-pools.inc
.. include:: os-floating-ips.inc
.. include:: os-security-groups.inc
@ -81,3 +80,4 @@ Compute API in the past, but no longer exist.
.. include:: os-virtual-interfaces.inc
.. include:: os-fixed-ips.inc
.. include:: os-floating-ips-bulk.inc
.. include:: os-floating-ip-dns.inc

View File

@ -11,11 +11,12 @@
Since these APIs are only implemented for **nova-network**, they are
deprecated. These will fail with a 404 starting from microversion 2.36.
They were removed in the 18.0.0 Rocky release.
Manages DNS records associated with floating IP addresses. The API
dispatches requests to a DNS driver that is selected at startup.
List Dns Domains
List DNS Domains
================
.. rest_method:: GET /os-floating-ip-dns
@ -24,7 +25,8 @@ Lists registered DNS domains published by the DNS drivers.
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403), notImplemented(501)
Error response codes: unauthorized(401), forbidden(403), gone(410),
notImplemented(501)
Response
--------
@ -34,7 +36,7 @@ Response
.. literalinclude:: ../../doc/api_samples/os-floating-ip-dns/floating-ip-dns-list-resp.json
:language: javascript
Create Or Update Dns Domain
Create Or Update DNS Domain
===========================
.. rest_method:: PUT /os-floating-ip-dns/{domain}
@ -43,8 +45,8 @@ Creates or updates a DNS domain.
Normal response codes: 200
Error response codes: badRequest(400), unauthorized(401),
forbidden(403), notImplemented(501)
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
gone(410), notImplemented(501)
Request
-------
@ -66,7 +68,7 @@ Response
.. literalinclude:: ../../doc/api_samples/os-floating-ip-dns/floating-ip-dns-create-or-update-resp.json
:language: javascript
Delete Dns Domain
Delete DNS Domain
=================
.. rest_method:: DELETE /os-floating-ip-dns/{domain}
@ -75,8 +77,8 @@ Deletes a DNS domain and all associated host entries.
Normal response codes: 202
Error response codes: unauthorized(401), forbidden(403),
itemNotFound(404), notImplemented(501)
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404),
gone(410), notImplemented(501)
Request
-------
@ -88,7 +90,7 @@ Request
Response
--------
List Dns Entries
List DNS Entries
================
.. rest_method:: GET /os-floating-ip-dns/{domain}/entries/{ip}
@ -97,8 +99,8 @@ Lists DNS entries for a domain and IP.
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403),
itemNotFound(404), notImplemented(501)
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404),
gone(410), notImplemented(501)
Request
-------
@ -111,12 +113,12 @@ Request
Response
--------
**Example List Dns Entries: JSON response**
**Example List DNS Entries: JSON response**
.. literalinclude:: ../../doc/api_samples/os-floating-ip-dns/floating-ip-dns-entry-list-resp.json
:language: javascript
Find Unique Dns Entry
Find Unique DNS Entry
=====================
.. rest_method:: GET /os-floating-ip-dns/{domain}/entries/{name}
@ -125,8 +127,8 @@ Finds a unique DNS entry for a domain and name.
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403),
itemNotFound(404), notImplemented(501)
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404),
gone(410), notImplemented(501)
Request
-------
@ -139,12 +141,12 @@ Request
Response
--------
**Example Find Unique Dns Entry: JSON response**
**Example Find Unique DNS Entry: JSON response**
.. literalinclude:: ../../doc/api_samples/os-floating-ip-dns/floating-ip-dns-entry-get-resp.json
:language: javascript
Create Or Update Dns Entry
Create Or Update DNS Entry
==========================
.. rest_method:: PUT /os-floating-ip-dns/{domain}/entries/{name}
@ -153,7 +155,8 @@ Creates or updates a DNS entry.
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403), notImplemented(501)
Error response codes: unauthorized(401), forbidden(403), gone(410),
notImplemented(501)
Request
-------
@ -163,7 +166,7 @@ Request
- domain: domain
- name: name
**Example Create Or Update Dns Entry: JSON request**
**Example Create Or Update DNS Entry: JSON request**
.. literalinclude:: ../../doc/api_samples/os-floating-ip-dns/floating-ip-dns-create-or-update-entry-req.json
:language: javascript
@ -171,12 +174,12 @@ Request
Response
--------
**Example Create Or Update Dns Entry: JSON response**
**Example Create Or Update DNS Entry: JSON response**
.. literalinclude:: ../../doc/api_samples/os-floating-ip-dns/floating-ip-dns-create-or-update-entry-resp.json
:language: javascript
Delete Dns Entry
Delete DNS Entry
================
.. rest_method:: DELETE /os-floating-ip-dns/{domain}/entries/{name}
@ -185,8 +188,8 @@ Deletes a DNS entry.
Normal response codes: 202
Error response codes: unauthorized(401), forbidden(403),
itemNotFound(404), notImplemented(501)
Error response codes: unauthorized(401), forbidden(403), itemNotFound(404),
gone(410), notImplemented(501)
Request
-------

View File

@ -12,242 +12,38 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import netutils
from six.moves import urllib
import webob
from webob import exc
from nova.api.openstack.api_version_request \
import MAX_PROXY_API_SUPPORT_VERSION
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import floating_ip_dns
from nova.api.openstack import wsgi
from nova.api import validation
from nova import exception
from nova.i18n import _
from nova import network
from nova.policies import floating_ip_dns as fid_policies
def _translate_dns_entry_view(dns_entry):
result = {}
result['ip'] = dns_entry.get('ip')
result['id'] = dns_entry.get('id')
result['type'] = dns_entry.get('type')
result['domain'] = dns_entry.get('domain')
result['name'] = dns_entry.get('name')
return {'dns_entry': result}
def _translate_dns_entries_view(dns_entries):
return {'dns_entries': [_translate_dns_entry_view(entry)['dns_entry']
for entry in dns_entries]}
def _translate_domain_entry_view(domain_entry):
result = {}
result['domain'] = domain_entry.get('domain')
result['scope'] = domain_entry.get('scope')
result['project'] = domain_entry.get('project')
result['availability_zone'] = domain_entry.get('availability_zone')
return {'domain_entry': result}
def _translate_domain_entries_view(domain_entries):
return {'domain_entries':
[_translate_domain_entry_view(entry)['domain_entry']
for entry in domain_entries]}
def _unquote_domain(domain):
"""Unquoting function for receiving a domain name in a URL.
Domain names tend to have .'s in them. Urllib doesn't quote dots,
but Routes tends to choke on them, so we need an extra level of
by-hand quoting here.
"""
return urllib.parse.unquote(domain).replace('%2E', '.')
def _create_dns_entry(ip, name, domain):
return {'ip': ip, 'name': name, 'domain': domain}
def _create_domain_entry(domain, scope=None, project=None, av_zone=None):
return {'domain': domain, 'scope': scope, 'project': project,
'availability_zone': av_zone}
class FloatingIPDNSDomainController(wsgi.Controller):
"""DNS domain controller for OpenStack API."""
def __init__(self):
super(FloatingIPDNSDomainController, self).__init__()
self.network_api = network.API()
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(501)
@wsgi.expected_errors(410)
def index(self, req):
"""Return a list of available DNS domains."""
context = req.environ['nova.context']
context.can(fid_policies.BASE_POLICY_NAME)
raise exc.HTTPGone()
try:
domains = self.network_api.get_dns_domains(context)
except NotImplementedError:
common.raise_feature_not_supported()
domainlist = [_create_domain_entry(domain['domain'],
domain.get('scope'),
domain.get('project'),
domain.get('availability_zone'))
for domain in domains]
return _translate_domain_entries_view(domainlist)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 501))
@validation.schema(floating_ip_dns.domain_entry_update)
@wsgi.expected_errors(410)
def update(self, req, id, body):
"""Add or modify domain entry."""
context = req.environ['nova.context']
context.can(fid_policies.POLICY_ROOT % "domain:update")
fqdomain = _unquote_domain(id)
entry = body['domain_entry']
scope = entry['scope']
project = entry.get('project', None)
av_zone = entry.get('availability_zone', None)
raise exc.HTTPGone()
if scope == 'private' and project:
msg = _("you can not pass project if the scope is private")
raise webob.exc.HTTPBadRequest(explanation=msg)
if scope == 'public' and av_zone:
msg = _("you can not pass av_zone if the scope is public")
raise webob.exc.HTTPBadRequest(explanation=msg)
if scope == 'private':
create_dns_domain = self.network_api.create_private_dns_domain
area_name, area = 'availability_zone', av_zone
else:
create_dns_domain = self.network_api.create_public_dns_domain
area_name, area = 'project', project
try:
create_dns_domain(context, fqdomain, area)
except NotImplementedError:
common.raise_feature_not_supported()
return _translate_domain_entry_view({'domain': fqdomain,
'scope': scope,
area_name: area})
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((404, 501))
@wsgi.response(202)
@wsgi.expected_errors(410)
def delete(self, req, id):
"""Delete the domain identified by id."""
context = req.environ['nova.context']
context.can(fid_policies.POLICY_ROOT % "domain:delete")
domain = _unquote_domain(id)
# Delete the whole domain
try:
self.network_api.delete_dns_domain(context, domain)
except NotImplementedError:
common.raise_feature_not_supported()
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
raise exc.HTTPGone()
class FloatingIPDNSEntryController(wsgi.Controller):
"""DNS Entry controller for OpenStack API."""
def __init__(self):
super(FloatingIPDNSEntryController, self).__init__()
self.network_api = network.API()
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((404, 501))
@wsgi.expected_errors(410)
def show(self, req, domain_id, id):
"""Return the DNS entry that corresponds to domain_id and id."""
context = req.environ['nova.context']
context.can(fid_policies.BASE_POLICY_NAME)
domain = _unquote_domain(domain_id)
raise exc.HTTPGone()
floating_ip = None
# Check whether id is a valid ipv4/ipv6 address.
if netutils.is_valid_ip(id):
floating_ip = id
try:
if floating_ip:
entries = self.network_api.get_dns_entries_by_address(context,
floating_ip,
domain)
else:
entries = self.network_api.get_dns_entries_by_name(context,
id,
domain)
except NotImplementedError:
common.raise_feature_not_supported()
if not entries:
explanation = _("DNS entries not found.")
raise webob.exc.HTTPNotFound(explanation=explanation)
if floating_ip:
entrylist = [_create_dns_entry(floating_ip, entry, domain)
for entry in entries]
dns_entries = _translate_dns_entries_view(entrylist)
return wsgi.ResponseObject(dns_entries)
entry = _create_dns_entry(entries[0], id, domain)
return _translate_dns_entry_view(entry)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(501)
@validation.schema(floating_ip_dns.dns_entry_update)
@wsgi.expected_errors(410)
def update(self, req, domain_id, id, body):
"""Add or modify dns entry."""
context = req.environ['nova.context']
context.can(fid_policies.BASE_POLICY_NAME)
domain = _unquote_domain(domain_id)
name = id
entry = body['dns_entry']
address = entry['ip']
dns_type = entry['dns_type']
raise exc.HTTPGone()
try:
entries = self.network_api.get_dns_entries_by_name(context,
name, domain)
if not entries:
# create!
self.network_api.add_dns_entry(context, address, name,
dns_type, domain)
else:
# modify!
self.network_api.modify_dns_entry(context, name,
address, domain)
except NotImplementedError:
common.raise_feature_not_supported()
return _translate_dns_entry_view({'ip': address,
'name': name,
'type': dns_type,
'domain': domain})
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((404, 501))
@wsgi.response(202)
@wsgi.expected_errors(410)
def delete(self, req, domain_id, id):
"""Delete the entry identified by req and id."""
context = req.environ['nova.context']
context.can(fid_policies.BASE_POLICY_NAME)
domain = _unquote_domain(domain_id)
name = id
try:
self.network_api.delete_dns_entry(context, name, domain)
except NotImplementedError:
common.raise_feature_not_supported()
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
raise exc.HTTPGone()

View File

@ -1,60 +0,0 @@
# Copyright 2014 IBM Corporation. All rights reserved.
#
# 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.
from nova.api.validation import parameter_types
domain_entry_update = {
'type': 'object',
'properties': {
'domain_entry': {
'type': 'object',
'properties': {
'scope': {
'type': 'string',
'enum': ['public', 'private'],
},
'project': parameter_types.project_id,
'availability_zone': parameter_types.name,
},
'required': ['scope'],
'maxProperties': 2,
'additionalProperties': False,
},
},
'required': ['domain_entry'],
'additionalProperties': False,
}
dns_entry_update = {
'type': 'object',
'properties': {
'dns_entry': {
'type': 'object',
'properties': {
'ip': parameter_types.ip_address,
'dns_type': {
'type': 'string',
'enum': ['a', 'A'],
},
},
'required': ['ip', 'dns_type'],
'additionalProperties': False,
},
},
'required': ['dns_entry'],
'additionalProperties': False,
}

View File

@ -41,7 +41,6 @@ from nova.policies import flavor_extra_specs
from nova.policies import flavor_manage
from nova.policies import flavor_rxtx
from nova.policies import flavors
from nova.policies import floating_ip_dns
from nova.policies import floating_ip_pools
from nova.policies import floating_ips
from nova.policies import hide_server_addresses
@ -115,7 +114,6 @@ def list_rules():
flavor_manage.list_rules(),
flavor_rxtx.list_rules(),
flavors.list_rules(),
floating_ip_dns.list_rules(),
floating_ip_pools.list_rules(),
floating_ips.list_rules(),
hide_server_addresses.list_rules(),

View File

@ -1,78 +0,0 @@
# Copyright 2016 Cloudbase Solutions Srl
# All Rights Reserved.
#
# 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.
from oslo_policy import policy
from nova.policies import base
BASE_POLICY_NAME = 'os_compute_api:os-floating-ip-dns'
POLICY_ROOT = 'os_compute_api:os-floating-ip-dns:%s'
floating_ip_dns_policies = [
policy.DocumentedRuleDefault(
BASE_POLICY_NAME,
base.RULE_ADMIN_OR_OWNER,
"""List registered DNS domains, and CRUD actions on domain names.
Note this only works with nova-network and this API is deprecated.""",
[
{
'method': 'GET',
'path': '/os-floating-ip-dns'
},
{
'method': 'GET',
'path': '/os-floating-ip-dns/{domain}/entries/{ip}'
},
{
'method': 'GET',
'path': '/os-floating-ip-dns/{domain}/entries/{name}'
},
{
'method': 'PUT',
'path': '/os-floating-ip-dns/{domain}/entries/{name}'
},
{
'method': 'DELETE',
'path': '/os-floating-ip-dns/{domain}/entries/{name}'
},
]),
policy.DocumentedRuleDefault(
POLICY_ROOT % 'domain:update',
base.RULE_ADMIN_API,
"Create or update a DNS domain.",
[
{
'method': 'PUT',
'path': '/os-floating-ip-dns/{domain}'
}
]),
policy.DocumentedRuleDefault(
POLICY_ROOT % 'domain:delete',
base.RULE_ADMIN_API,
"Delete a DNS domain.",
[
{
'method': 'DELETE',
'path': '/os-floating-ip-dns/{domain}'
}
]),
]
def list_rules():
return floating_ip_dns_policies

View File

@ -1,6 +0,0 @@
{
"dns_entry": {
"ip": "%(ip)s",
"dns_type": "%(dns_type)s"
}
}

View File

@ -1,9 +0,0 @@
{
"dns_entry": {
"domain": "%(domain)s",
"id": null,
"ip": "%(ip)s",
"name": "%(name)s",
"type": "%(dns_type)s"
}
}

View File

@ -1,6 +0,0 @@
{
"domain_entry": {
"scope": "%(scope)s",
"project": "%(project)s"
}
}

View File

@ -1,8 +0,0 @@
{
"domain_entry": {
"availability_zone": null,
"domain": "%(domain)s",
"project": "%(project)s",
"scope": "%(scope)s"
}
}

View File

@ -1,9 +0,0 @@
{
"dns_entry": {
"domain": "%(domain)s",
"id": null,
"ip": "%(ip)s",
"name": "%(name)s",
"type": null
}
}

View File

@ -1,11 +0,0 @@
{
"dns_entries": [
{
"domain": "%(domain)s",
"id": null,
"ip": "%(ip)s",
"name": "%(name)s",
"type": null
}
]
}

View File

@ -1,10 +0,0 @@
{
"domain_entries": [
{
"availability_zone": null,
"domain": "%(domain)s",
"project": "%(project)s",
"scope": "%(scope)s"
}
]
}

View File

@ -12,81 +12,59 @@
# License for the specific language governing permissions and limitations
# under the License.
from nova.tests.functional.api import client as api_client
from nova.tests.functional.api_sample_tests import api_sample_base
class FloatingIpDNSTest(api_sample_base.ApiSampleTestBaseV21):
ADMIN_API = True
sample_dir = "os-floating-ip-dns"
domain = 'domain1.example.org'
name = 'instance1'
scope = 'public'
project = 'project1'
dns_type = 'A'
ip = '192.168.1.1'
def _create_or_update(self):
subs = {'project': self.project,
'scope': self.scope}
response = self._do_put('os-floating-ip-dns/%s' % self.domain,
'floating-ip-dns-create-or-update-req', subs)
subs.update({'domain': self.domain})
self._verify_response('floating-ip-dns-create-or-update-resp', subs,
response, 200)
def _create_or_update_entry(self):
subs = {'ip': self.ip, 'dns_type': self.dns_type}
response = self._do_put('os-floating-ip-dns/%s/entries/%s'
% (self.domain, self.name),
'floating-ip-dns-create-or-update-entry-req',
subs)
subs.update({'name': self.name, 'domain': self.domain})
self._verify_response('floating-ip-dns-create-or-update-entry-resp',
subs, response, 200)
def test_floating_ip_dns_list(self):
self._create_or_update()
response = self._do_get('os-floating-ip-dns')
subs = {'domain': self.domain,
'project': self.project,
'scope': self.scope}
self._verify_response('floating-ip-dns-list-resp', subs,
response, 200)
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.api_get,
'os-floating-ip-dns')
self.assertEqual(410, ex.response.status_code)
def test_floating_ip_dns_create_or_update(self):
self._create_or_update()
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.api_put,
'os-floating-ip-dns/domain1.example.org',
{'project': 'project1',
'scope': 'public'})
self.assertEqual(410, ex.response.status_code)
def test_floating_ip_dns_delete(self):
self._create_or_update()
response = self._do_delete('os-floating-ip-dns/%s' % self.domain)
self.assertEqual(202, response.status_code)
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.api_delete,
'os-floating-ip-dns/domain1.example.org')
self.assertEqual(410, ex.response.status_code)
def test_floating_ip_dns_create_or_update_entry(self):
self._create_or_update_entry()
url = 'os-floating-ip-dns/domain1.example.org/entries/instance1'
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.api_put,
url,
{'ip': '192.168.1.1',
'dns_type': 'A'})
self.assertEqual(410, ex.response.status_code)
def test_floating_ip_dns_entry_get(self):
self._create_or_update_entry()
response = self._do_get('os-floating-ip-dns/%s/entries/%s'
% (self.domain, self.name))
subs = {'domain': self.domain,
'ip': self.ip,
'name': self.name}
self._verify_response('floating-ip-dns-entry-get-resp', subs,
response, 200)
url = 'os-floating-ip-dns/domain1.example.org/entries/instance1'
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.api_get,
url)
self.assertEqual(410, ex.response.status_code)
def test_floating_ip_dns_entry_delete(self):
self._create_or_update_entry()
response = self._do_delete('os-floating-ip-dns/%s/entries/%s'
% (self.domain, self.name))
self.assertEqual(202, response.status_code)
url = 'os-floating-ip-dns/domain1.example.org/entries/instance1'
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.api_delete,
url)
self.assertEqual(410, ex.response.status_code)
def test_floating_ip_dns_entry_list(self):
self._create_or_update_entry()
response = self._do_get('os-floating-ip-dns/%s/entries/%s'
% (self.domain, self.ip))
subs = {'domain': self.domain,
'ip': self.ip,
'name': self.name}
self._verify_response('floating-ip-dns-entry-list-resp', subs,
response, 200)
url = 'os-floating-ip-dns/domain1.example.org/entries/192.168.1.1'
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.api_get,
url)
self.assertEqual(410, ex.response.status_code)

View File

@ -1,453 +0,0 @@
# Copyright 2011 Andrew Bogott for the Wikimedia Foundation
# All Rights Reserved.
# Copyright 2013 Red Hat, Inc.
#
# 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
from six.moves import urllib
import webob
from nova.api.openstack.compute import floating_ip_dns \
as fipdns_v21
from nova import context
from nova.db import api as db
from nova import exception
from nova import network
from nova import test
from nova.tests.unit.api.openstack import fakes
name = "arbitraryname"
name2 = "anotherarbitraryname"
test_ipv4_address = '10.0.0.66'
test_ipv4_address2 = '10.0.0.67'
test_ipv6_address = 'fe80:0:0:0:0:0:a00:42'
domain = "example.org"
domain2 = "example.net"
floating_ip_id = '1'
def _quote_domain(domain):
"""Domain names tend to have .'s in them. Urllib doesn't quote dots,
but Routes tends to choke on them, so we need an extra level of
by-hand quoting here. This function needs to duplicate the one in
python-novaclient/novaclient/v1_1/floating_ip_dns.py
"""
return urllib.parse.quote(domain.replace('.', '%2E'))
def network_api_get_floating_ip(self, context, id):
return {'id': floating_ip_id, 'address': test_ipv4_address,
'fixed_ip': None}
def network_get_dns_domains(self, context):
return [{'domain': 'example.org', 'scope': 'public'},
{'domain': 'example.com', 'scope': 'public',
'project': 'project1'},
{'domain': 'private.example.com', 'scope': 'private',
'availability_zone': 'avzone'}]
def network_get_dns_entries_by_address(self, context, address, domain):
return [name, name2]
def network_get_dns_entries_by_name(self, context, address, domain):
return [test_ipv4_address]
def network_add_dns_entry(self, context, address, name, dns_type, domain):
return {'dns_entry': {'ip': test_ipv4_address,
'name': name,
'type': dns_type,
'domain': domain}}
def network_modify_dns_entry(self, context, address, name, domain):
return {'dns_entry': {'name': name,
'ip': address,
'domain': domain}}
def network_create_private_dns_domain(self, context, domain, avail_zone):
pass
def network_create_public_dns_domain(self, context, domain, project):
pass
class FloatingIpDNSTestV21(test.TestCase):
floating_ip_dns = fipdns_v21
def _create_floating_ip(self):
"""Create a floating ip object."""
host = "fake_host"
db.floating_ip_create(self.context,
{'address': test_ipv4_address,
'host': host})
db.floating_ip_create(self.context,
{'address': test_ipv6_address,
'host': host})
def _delete_floating_ip(self):
db.floating_ip_destroy(self.context, test_ipv4_address)
db.floating_ip_destroy(self.context, test_ipv6_address)
def _check_status(self, expected_status, res, controller_method):
self.assertEqual(expected_status, controller_method.wsgi_code)
def _bad_request(self):
return webob.exc.HTTPBadRequest
def setUp(self):
super(FloatingIpDNSTestV21, self).setUp()
# None of these APIs are implemented for Neutron.
self.flags(use_neutron=False)
self.stub_out("nova.network.api.API.get_dns_domains",
network_get_dns_domains)
self.stub_out("nova.network.api.API.get_dns_entries_by_address",
network_get_dns_entries_by_address)
self.stub_out("nova.network.api.API.get_dns_entries_by_name",
network_get_dns_entries_by_name)
self.stub_out("nova.network.api.API.get_floating_ip",
network_api_get_floating_ip)
self.stub_out("nova.network.api.API.add_dns_entry",
network_add_dns_entry)
self.stub_out("nova.network.api.API.modify_dns_entry",
network_modify_dns_entry)
self.stub_out("nova.network.api.API.create_public_dns_domain",
network_create_public_dns_domain)
self.stub_out("nova.network.api.API.create_private_dns_domain",
network_create_private_dns_domain)
self.context = context.get_admin_context()
self._create_floating_ip()
temp = self.floating_ip_dns.FloatingIPDNSDomainController()
self.domain_controller = temp
self.entry_controller = self.floating_ip_dns.\
FloatingIPDNSEntryController()
self.admin_req = fakes.HTTPRequest.blank('', use_admin_context=True)
self.req = fakes.HTTPRequest.blank('')
def tearDown(self):
self._delete_floating_ip()
super(FloatingIpDNSTestV21, self).tearDown()
def test_dns_domains_list(self):
res_dict = self.domain_controller.index(self.req)
entries = res_dict['domain_entries']
self.assertTrue(entries)
self.assertEqual(entries[0]['domain'], "example.org")
self.assertFalse(entries[0]['project'])
self.assertFalse(entries[0]['availability_zone'])
self.assertEqual(entries[1]['domain'], "example.com")
self.assertEqual(entries[1]['project'], "project1")
self.assertFalse(entries[1]['availability_zone'])
self.assertEqual(entries[2]['domain'], "private.example.com")
self.assertFalse(entries[2]['project'])
self.assertEqual(entries[2]['availability_zone'], "avzone")
def _test_get_dns_entries_by_address(self, address):
entries = self.entry_controller.show(self.req, _quote_domain(domain),
address)
entries = entries.obj
self.assertEqual(len(entries['dns_entries']), 2)
self.assertEqual(entries['dns_entries'][0]['name'],
name)
self.assertEqual(entries['dns_entries'][1]['name'],
name2)
self.assertEqual(entries['dns_entries'][0]['domain'],
domain)
def test_get_dns_entries_by_ipv4_address(self):
self._test_get_dns_entries_by_address(test_ipv4_address)
def test_get_dns_entries_by_ipv6_address(self):
self._test_get_dns_entries_by_address(test_ipv6_address)
def test_get_dns_entries_by_name(self):
entry = self.entry_controller.show(self.req, _quote_domain(domain),
name)
self.assertEqual(entry['dns_entry']['ip'],
test_ipv4_address)
self.assertEqual(entry['dns_entry']['domain'],
domain)
@mock.patch.object(network.api.API, "get_dns_entries_by_name",
side_effect=webob.exc.HTTPNotFound())
def test_dns_entries_not_found(self, mock_get_entries):
self.assertRaises(webob.exc.HTTPNotFound,
self.entry_controller.show,
self.req, _quote_domain(domain), 'nonexistent')
self.assertTrue(mock_get_entries.called)
def test_create_entry(self):
body = {'dns_entry':
{'ip': test_ipv4_address,
'dns_type': 'A'}}
entry = self.entry_controller.update(self.req, _quote_domain(domain),
name, body=body)
self.assertEqual(entry['dns_entry']['ip'], test_ipv4_address)
def test_create_domain(self):
self._test_create_domain(self.req)
def _test_create_domain(self, req):
body = {'domain_entry':
{'scope': 'private',
'project': 'testproject'}}
self.assertRaises(self._bad_request(),
self.domain_controller.update, req,
_quote_domain(domain), body=body)
body = {'domain_entry':
{'scope': 'public',
'availability_zone': 'zone1'}}
self.assertRaises(self._bad_request(),
self.domain_controller.update, req,
_quote_domain(domain), body=body)
body = {'domain_entry':
{'scope': 'public',
'project': 'testproject'}}
entry = self.domain_controller.update(req,
_quote_domain(domain), body=body)
self.assertEqual(entry['domain_entry']['domain'], domain)
self.assertEqual(entry['domain_entry']['scope'], 'public')
self.assertEqual(entry['domain_entry']['project'], 'testproject')
body = {'domain_entry':
{'scope': 'private',
'availability_zone': 'zone1'}}
entry = self.domain_controller.update(req,
_quote_domain(domain), body=body)
self.assertEqual(entry['domain_entry']['domain'], domain)
self.assertEqual(entry['domain_entry']['scope'], 'private')
self.assertEqual(entry['domain_entry']['availability_zone'], 'zone1')
@mock.patch.object(network.api.API, "delete_dns_entry")
def test_delete_entry(self, mock_del_entry):
delete = self.entry_controller.delete
res = delete(self.req, _quote_domain(domain), name)
self._check_status(202, res, delete)
mock_del_entry.assert_called_once_with(mock.ANY, name, domain)
@mock.patch.object(network.api.API, "delete_dns_entry",
side_effect=exception.NotFound)
def test_delete_entry_notfound(self, mock_del_entry):
self.assertRaises(webob.exc.HTTPNotFound,
self.entry_controller.delete, self.req, _quote_domain(domain),
name)
self.assertTrue(mock_del_entry.called)
def test_delete_domain(self):
self._test_delete_domain(self.req)
@mock.patch.object(network.api.API, "delete_dns_domain")
def _test_delete_domain(self, req, mock_del_dom):
delete = self.domain_controller.delete
res = delete(req, _quote_domain(domain))
self._check_status(202, res, delete)
mock_del_dom.assert_called_once_with(mock.ANY, domain)
def test_delete_domain_notfound(self):
self._test_delete_domain_notfound(self.req)
@mock.patch.object(network.api.API, "delete_dns_domain",
side_effect=exception.NotFound)
def _test_delete_domain_notfound(self, req, mock_del_dom):
self.assertRaises(
webob.exc.HTTPNotFound, self.domain_controller.delete,
req, _quote_domain(domain))
self.assertTrue(mock_del_dom.called)
def test_modify(self):
body = {'dns_entry':
{'ip': test_ipv4_address2,
'dns_type': 'A'}}
entry = self.entry_controller.update(self.req, domain, name, body=body)
self.assertEqual(entry['dns_entry']['ip'], test_ipv4_address2)
def test_not_implemented_dns_entry_update(self):
body = {'dns_entry':
{'ip': test_ipv4_address,
'dns_type': 'A'}}
with mock.patch.object(network.api.API, 'modify_dns_entry',
side_effect=NotImplementedError()):
self.assertRaises(webob.exc.HTTPNotImplemented,
self.entry_controller.update, self.req,
_quote_domain(domain), name, body=body)
def test_not_implemented_dns_entry_show(self):
with mock.patch.object(network.api.API, 'get_dns_entries_by_name',
side_effect=NotImplementedError()):
self.assertRaises(webob.exc.HTTPNotImplemented,
self.entry_controller.show,
self.req, _quote_domain(domain), name)
def test_not_implemented_delete_entry(self):
with mock.patch.object(network.api.API, 'delete_dns_entry',
side_effect=NotImplementedError()):
self.assertRaises(webob.exc.HTTPNotImplemented,
self.entry_controller.delete, self.req,
_quote_domain(domain), name)
def test_not_implemented_delete_domain(self):
with mock.patch.object(network.api.API, 'delete_dns_domain',
side_effect=NotImplementedError()):
self.assertRaises(webob.exc.HTTPNotImplemented,
self.domain_controller.delete, self.admin_req,
_quote_domain(domain))
def test_not_implemented_create_domain(self):
body = {'domain_entry':
{'scope': 'private',
'availability_zone': 'zone1'}}
with mock.patch.object(network.api.API, 'create_private_dns_domain',
side_effect=NotImplementedError()):
self.assertRaises(webob.exc.HTTPNotImplemented,
self.domain_controller.update, self.admin_req,
_quote_domain(domain), body=body)
def test_not_implemented_dns_domains_list(self):
with mock.patch.object(network.api.API, 'get_dns_domains',
side_effect=NotImplementedError()):
self.assertRaises(webob.exc.HTTPNotImplemented,
self.domain_controller.index, self.req)
class FloatingIPDNSDomainPolicyEnforcementV21(test.NoDBTestCase):
def setUp(self):
super(FloatingIPDNSDomainPolicyEnforcementV21, self).setUp()
self.controller = fipdns_v21.FloatingIPDNSDomainController()
self.rule_name = "os_compute_api:os-floating-ip-dns"
self.policy.set_rules({self.rule_name: "project:non_fake"})
self.req = fakes.HTTPRequest.blank('')
def test_get_floating_ip_dns_policy_failed(self):
rule_name = "os_compute_api:os-floating-ip-dns"
self.policy.set_rules({rule_name: "project:non_fake"})
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller.index, self.req)
self.assertEqual(
"Policy doesn't allow %s to be performed." % rule_name,
exc.format_message())
def test_update_floating_ip_dns_policy_failed(self):
rule_name = "os_compute_api:os-floating-ip-dns:domain:update"
self.policy.set_rules({rule_name: "project:non_fake"})
body = {'domain_entry':
{'scope': 'public',
'project': 'testproject'}}
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller.update, self.req, _quote_domain(domain), body=body)
self.assertEqual(
"Policy doesn't allow %s to be performed." % rule_name,
exc.format_message())
def test_delete_floating_ip_dns_policy_failed(self):
rule_name = "os_compute_api:os-floating-ip-dns:domain:delete"
self.policy.set_rules({rule_name: "project:non_fake"})
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller.delete, self.req, _quote_domain(domain))
self.assertEqual(
"Policy doesn't allow %s to be performed." % rule_name,
exc.format_message())
class FloatingIPDNSEntryPolicyEnforcementV21(test.NoDBTestCase):
def setUp(self):
super(FloatingIPDNSEntryPolicyEnforcementV21, self).setUp()
self.controller = fipdns_v21.FloatingIPDNSEntryController()
self.rule_name = "os_compute_api:os-floating-ip-dns"
self.policy.set_rules({self.rule_name: "project:non_fake"})
self.req = fakes.HTTPRequest.blank('')
def test_show_floating_ip_dns_entry_policy_failed(self):
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller.show, self.req,
_quote_domain(domain), test_ipv4_address)
self.assertEqual(
"Policy doesn't allow %s to be performed." % self.rule_name,
exc.format_message())
def test_update_floating_ip_dns_policy_failed(self):
body = {'dns_entry':
{'ip': test_ipv4_address,
'dns_type': 'A'}}
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller.update, self.req, _quote_domain(domain),
name, body=body)
self.assertEqual(
"Policy doesn't allow %s to be performed." % self.rule_name,
exc.format_message())
def test_delete_floating_ip_dns_policy_failed(self):
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller.delete, self.req, _quote_domain(domain), name)
self.assertEqual(
"Policy doesn't allow %s to be performed." % self.rule_name,
exc.format_message())
class FloatingIpDNSDomainDeprecationTest(test.NoDBTestCase):
def setUp(self):
super(FloatingIpDNSDomainDeprecationTest, self).setUp()
self.controller = fipdns_v21.FloatingIPDNSDomainController()
self.req = fakes.HTTPRequest.blank('', version='2.36')
def test_all_apis_return_not_found(self):
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.index, self.req)
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.update, self.req, fakes.FAKE_UUID, {})
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.delete, self.req, fakes.FAKE_UUID)
class FloatingIpDNSEntryDeprecationTest(test.NoDBTestCase):
def setUp(self):
super(FloatingIpDNSEntryDeprecationTest, self).setUp()
self.controller = fipdns_v21.FloatingIPDNSEntryController()
self.req = fakes.HTTPRequest.blank('', version='2.36')
def test_all_apis_return_not_found(self):
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.show, self.req, fakes.FAKE_UUID, fakes.FAKE_UUID)
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.update, self.req, fakes.FAKE_UUID, fakes.FAKE_UUID,
{})
self.assertRaises(exception.VersionNotFoundForAPIMethod,
self.controller.delete, self.req, fakes.FAKE_UUID, fakes.FAKE_UUID)

View File

@ -46,9 +46,6 @@ policy_data = """
"os_compute_api:os-flavor-manage": "",
"os_compute_api:os-flavor-manage:create": "",
"os_compute_api:os-flavor-manage:delete": "",
"os_compute_api:os-floating-ip-dns": "",
"os_compute_api:os-floating-ip-dns:domain:update": "",
"os_compute_api:os-floating-ip-dns:domain:delete": "",
"os_compute_api:os-floating-ip-pools": "",
"os_compute_api:os-floating-ips": "",
"os_compute_api:os-instance-actions": "",

View File

@ -311,8 +311,6 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
"os_compute_api:os-flavor-manage:create",
"os_compute_api:os-flavor-manage:update",
"os_compute_api:os-flavor-manage:delete",
"os_compute_api:os-floating-ip-dns:domain:delete",
"os_compute_api:os-floating-ip-dns:domain:update",
"os_compute_api:os-hosts",
"os_compute_api:os-hypervisors",
"os_compute_api:os-instance-actions:events",
@ -408,7 +406,6 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
"os_compute_api:flavors",
"os_compute_api:os-flavor-extra-specs:index",
"os_compute_api:os-flavor-extra-specs:show",
"os_compute_api:os-floating-ip-dns",
"os_compute_api:os-floating-ip-pools",
"os_compute_api:os-floating-ips",
"os_compute_api:image-size",

View File

@ -16,6 +16,13 @@ upgrade:
* ``GET /os-floating-ips-bulk/{host_name}``
* ``POST /os-floating-ips-bulk``
* ``PUT /os-floating-ips-bulk/delete``
* ``GET /os-floating-ip-dns``
* ``PUT /os-floating-ip-dns/{domain}``
* ``DELETE /os-floating-ip-dns/{domain}``
* ``GET /os-floating-ip-dns/{domain}/entries/{ip}``
* ``GET /os-floating-ip-dns/{domain}/entries/{name}``
* ``PUT /os-floating-ip-dns/{domain}/entries/{name}``
* ``DELETE /os-floating-ip-dns/{domain}/entries/{name}``
In addition, the following configuration options have been removed.