Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This patch introduces API definition for dns-integration extension. Change-Id: I324fe5b4e60f3dbf887c16ea986f31404ab906a6
- Loading branch information
Hirofumi Ichihara
committed
Jun 16, 2017
1 parent
1401560
commit 1412f1e
Showing
8 changed files
with
532 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# 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 neutron_lib.api import converters as convert | ||
from neutron_lib.api.definitions import l3 | ||
from neutron_lib.api.definitions import network | ||
from neutron_lib.api.definitions import port | ||
from neutron_lib.api import validators | ||
from neutron_lib.api.validators import dns as dns_validator | ||
from neutron_lib.db import constants | ||
|
||
# The alias of the extension. | ||
ALIAS = 'dns-integration' | ||
|
||
# Whether or not this extension is simply signaling behavior to the user | ||
# or it actively modifies the attribute map (mandatory). | ||
IS_SHIM_EXTENSION = False | ||
|
||
# Whether the extension is marking the adoption of standardattr model for | ||
# legacy resources, or introducing new standardattr attributes. False or | ||
# None if the standardattr model is adopted since the introduction of | ||
# resource extension (mandatory). | ||
# If this is True, the alias for the extension should be prefixed with | ||
# 'standard-attr-'. | ||
IS_STANDARD_ATTR_EXTENSION = False | ||
|
||
# The name of the extension (mandatory). | ||
NAME = 'DNS Integration' | ||
|
||
# A prefix for API resources. An empty prefix means that the API is going | ||
# to be exposed at the v2/ level as any other core resource (mandatory). | ||
API_PREFIX = '' | ||
|
||
# The description of the extension (mandatory). | ||
DESCRIPTION = "Provides integration with DNS." | ||
|
||
# A timestamp of when the extension was introduced (mandatory). | ||
UPDATED_TIMESTAMP = "2015-08-15T18:00:00-00:00" | ||
|
||
DNSNAME = 'dns_name' | ||
DNSDOMAIN = 'dns_domain' | ||
DNSASSIGNMENT = 'dns_assignment' | ||
|
||
validators.add_validator('dns_host_name', dns_validator.validate_dns_name) | ||
validators.add_validator('fip_dns_host_name', | ||
dns_validator.validate_fip_dns_name) | ||
validators.add_validator('dns_domain_name', | ||
dns_validator.validate_dns_domain) | ||
|
||
# The resource attribute map for the extension. It is effectively the | ||
# bulk of the API contract alongside ACTION_MAP (mandatory). | ||
RESOURCE_ATTRIBUTE_MAP = { | ||
port.COLLECTION_NAME: { | ||
DNSNAME: {'allow_post': True, 'allow_put': True, | ||
'default': '', | ||
'convert_to': convert.convert_string_to_case_insensitive, | ||
'validate': {'type:dns_host_name': | ||
constants.FQDN_FIELD_SIZE}, | ||
'is_visible': True}, | ||
DNSASSIGNMENT: {'allow_post': False, 'allow_put': False, | ||
'is_visible': True}, | ||
}, | ||
l3.FLOATINGIPS: { | ||
DNSNAME: {'allow_post': True, 'allow_put': False, | ||
'default': '', | ||
'convert_to': convert.convert_string_to_case_insensitive, | ||
'validate': {'type:fip_dns_host_name': | ||
constants.FQDN_FIELD_SIZE}, | ||
'is_visible': True}, | ||
DNSDOMAIN: {'allow_post': True, 'allow_put': False, | ||
'default': '', | ||
'convert_to': convert.convert_string_to_case_insensitive, | ||
'validate': {'type:dns_domain_name': | ||
constants.FQDN_FIELD_SIZE}, | ||
'is_visible': True}, | ||
}, | ||
network.COLLECTION_NAME: { | ||
DNSDOMAIN: {'allow_post': True, 'allow_put': True, | ||
'default': '', | ||
'convert_to': convert.convert_string_to_case_insensitive, | ||
'validate': {'type:dns_domain_name': | ||
constants.FQDN_FIELD_SIZE}, | ||
'is_visible': True}, | ||
}, | ||
} | ||
|
||
# The subresource attribute map for the extension. It adds child resources | ||
# to main extension's resource. The subresource map must have a parent and | ||
# a parameters entry. If an extension does not need such a map, None can | ||
# be specified (mandatory). For example: | ||
SUB_RESOURCE_ATTRIBUTE_MAP = {} | ||
|
||
# The action map: it associates verbs with methods to be performed on | ||
# the API resource (mandatory). | ||
ACTION_MAP = {} | ||
|
||
# The action status: it associates response statuses with methods to be | ||
# performed on the API resource (mandatory). | ||
ACTION_STATUS = {} | ||
|
||
# The list of required extensions (mandatory). | ||
REQUIRED_EXTENSIONS = [l3.ALIAS] | ||
|
||
# The list of optional extensions (mandatory). | ||
OPTIONAL_EXTENSIONS = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# 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 re | ||
|
||
from oslo_config import cfg | ||
|
||
from neutron_lib._i18n import _ | ||
from neutron_lib.api import validators | ||
from neutron_lib import constants | ||
from neutron_lib.db import constants as db_constants | ||
|
||
|
||
def _validate_dns_format(data, max_len=db_constants.FQDN_FIELD_SIZE): | ||
# NOTE: An individual name regex instead of an entire FQDN was used | ||
# because its easier to make correct. The logic should validate that the | ||
# dns_name matches RFC 1123 (section 2.1) and RFC 952. | ||
if not data: | ||
return | ||
try: | ||
# A trailing period is allowed to indicate that a name is fully | ||
# qualified per RFC 1034 (page 7). | ||
trimmed = data[:-1] if data.endswith('.') else data | ||
if len(trimmed) > max_len: | ||
raise TypeError( | ||
_("'%(trimmed)s' exceeds the %(maxlen)s character FQDN " | ||
"limit") % {'trimmed': trimmed, 'maxlen': max_len}) | ||
labels = trimmed.split('.') | ||
for label in labels: | ||
if not label: | ||
raise TypeError(_("Encountered an empty component")) | ||
if label.endswith('-') or label.startswith('-'): | ||
raise TypeError( | ||
_("Name '%s' must not start or end with a hyphen") % label) | ||
if not re.match(constants.DNS_LABEL_REGEX, label): | ||
raise TypeError( | ||
_("Name '%s' must be 1-63 characters long, each of " | ||
"which can only be alphanumeric or a hyphen") % label) | ||
# RFC 1123 hints that a TLD can't be all numeric. last is a TLD if | ||
# it's an FQDN. | ||
if len(labels) > 1 and re.match("^[0-9]+$", labels[-1]): | ||
raise TypeError( | ||
_("TLD '%s' must not be all numeric") % labels[-1]) | ||
except TypeError as e: | ||
msg = _("'%(data)s' not a valid PQDN or FQDN. Reason: %(reason)s") % { | ||
'data': data, 'reason': e} | ||
return msg | ||
|
||
|
||
def _validate_dns_name_with_dns_domain(request_dns_name, dns_domain): | ||
# If a PQDN was passed, make sure the FQDN that will be generated is of | ||
# legal size | ||
higher_labels = dns_domain | ||
if dns_domain: | ||
higher_labels = '.%s' % dns_domain | ||
higher_labels_len = len(higher_labels) | ||
dns_name_len = len(request_dns_name) | ||
if not request_dns_name.endswith('.'): | ||
if dns_name_len + higher_labels_len > db_constants.FQDN_FIELD_SIZE: | ||
msg = _("The dns_name passed is a PQDN and its size is " | ||
"'%(dns_name_len)s'. The dns_domain option in " | ||
"neutron.conf is set to %(dns_domain)s, with a " | ||
"length of '%(higher_labels_len)s'. When the two are " | ||
"concatenated to form a FQDN (with a '.' at the end), " | ||
"the resulting length exceeds the maximum size " | ||
"of '%(fqdn_max_len)s'" | ||
) % {'dns_name_len': dns_name_len, | ||
'dns_domain': cfg.CONF.dns_domain, | ||
'higher_labels_len': higher_labels_len, | ||
'fqdn_max_len': db_constants.FQDN_FIELD_SIZE} | ||
return msg | ||
return | ||
|
||
# A FQDN was passed | ||
if (dns_name_len <= higher_labels_len or not | ||
request_dns_name.endswith(higher_labels)): | ||
msg = _("The dns_name passed is a FQDN. Its higher level labels " | ||
"must be equal to the dns_domain option in neutron.conf, " | ||
"that has been set to '%(dns_domain)s'. It must also " | ||
"include one or more valid DNS labels to the left " | ||
"of '%(dns_domain)s'") % {'dns_domain': | ||
cfg.CONF.dns_domain} | ||
return msg | ||
|
||
|
||
def _get_dns_domain_config(): | ||
if not cfg.CONF.dns_domain: | ||
return '' | ||
if cfg.CONF.dns_domain.endswith('.'): | ||
return cfg.CONF.dns_domain | ||
return '%s.' % cfg.CONF.dns_domain | ||
|
||
|
||
def _get_request_dns_name(dns_name): | ||
dns_domain = _get_dns_domain_config() | ||
if (dns_domain and dns_domain != constants.DNS_DOMAIN_DEFAULT): | ||
# If CONF.dns_domain is the default value 'openstacklocal', | ||
# neutron don't let the user to assign dns_name to ports | ||
return dns_name | ||
return '' | ||
|
||
|
||
def validate_dns_name(data, max_len=db_constants.FQDN_FIELD_SIZE): | ||
"""Validate DNS name. | ||
This method validates dns name and also needs to have dns_domain in config | ||
because this may call a method which uses the config. | ||
:param data: The data to validate. | ||
:param max_len: An optional cap on the length of the string. | ||
:returns: None if data is valid, otherwise a human readable message | ||
indicating why validation failed. | ||
""" | ||
msg = _validate_dns_format(data, max_len) | ||
if msg: | ||
return msg | ||
|
||
request_dns_name = _get_request_dns_name(data) | ||
if request_dns_name: | ||
dns_domain = _get_dns_domain_config() | ||
msg = _validate_dns_name_with_dns_domain(request_dns_name, dns_domain) | ||
if msg: | ||
return msg | ||
|
||
|
||
def validate_fip_dns_name(data, max_len=db_constants.FQDN_FIELD_SIZE): | ||
"""Validate DNS name for floating IP. | ||
:param data: The data to validate. | ||
:param max_len: An optional cap on the length of the string. | ||
:returns: None if data is valid, otherwise a human readable message | ||
indicating why validation failed. | ||
""" | ||
msg = validators.validate_string(data) | ||
if msg: | ||
return msg | ||
if not data: | ||
return | ||
if data.endswith('.'): | ||
msg = _("'%s' is a FQDN. It should be a relative domain name") % data | ||
return msg | ||
msg = _validate_dns_format(data, max_len) | ||
if msg: | ||
return msg | ||
length = len(data) | ||
if length > max_len - 3: | ||
msg = _("'%(data)s' contains %(length)s characters. Adding a " | ||
"domain name will cause it to exceed the maximum length " | ||
"of a FQDN of '%(max_len)s'") % {"data": data, | ||
"length": length, | ||
"max_len": max_len} | ||
return msg | ||
|
||
|
||
def validate_dns_domain(data, max_len=db_constants.FQDN_FIELD_SIZE): | ||
"""Validate DNS domain. | ||
:param data: The data to validate. | ||
:param max_len: An optional cap on the length of the string. | ||
:returns: None if data is valid, otherwise a human readable message | ||
indicating why validation failed. | ||
""" | ||
msg = validators.validate_string(data) | ||
if msg: | ||
return msg | ||
if not data: | ||
return | ||
if not data.endswith('.'): | ||
msg = _("'%s' is not a FQDN") % data | ||
return msg | ||
msg = _validate_dns_format(data, max_len) | ||
if msg: | ||
return msg | ||
length = len(data) | ||
if length > max_len - 2: | ||
msg = _("'%(data)s' contains %(length)s characters. Adding a " | ||
"sub-domain will cause it to exceed the maximum length of a " | ||
"FQDN of '%(max_len)s'") % {"data": data, | ||
"length": length, | ||
"max_len": max_len} | ||
return msg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# 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 neutron_lib._i18n import _ | ||
from neutron_lib import exceptions | ||
|
||
|
||
class DNSDomainNotFound(exceptions.NotFound): | ||
message = _("Domain %(dns_domain)s not found in the external DNS service") | ||
|
||
|
||
class DuplicateRecordSet(exceptions.Conflict): | ||
message = _("Name %(dns_name)s is duplicated in the external DNS service") | ||
|
||
|
||
class ExternalDNSDriverNotFound(exceptions.NotFound): | ||
message = _("External DNS driver %(driver)s could not be found.") | ||
|
||
|
||
class InvalidPTRZoneConfiguration(exceptions.Conflict): | ||
message = _("Value of %(parameter)s has to be multiple of %(number)s, " | ||
"with maximum value of %(maximum)s and minimum value of " | ||
"%(minimum)s") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# 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 neutron_lib.api.definitions import dns | ||
from neutron_lib.api.definitions import l3 | ||
from neutron_lib.tests.unit.api.definitions import base | ||
|
||
|
||
class DnsDefinitionTestCase(base.DefinitionBaseTestCase): | ||
extension_module = dns | ||
extension_resources = (l3.FLOATINGIPS,) | ||
extension_attributes = (dns.DNSNAME, dns.DNSDOMAIN, dns.DNSASSIGNMENT,) |
Oops, something went wrong.