Add address format check for property

Some resource receive IP address as property, this patch adds
custom constraint for IP address format validation.

Implemented: blueprint enhance-property-constraints
Change-Id: Ie4ad71418aa355a4e24ac6c2a33bd85c19c5ef11
This commit is contained in:
Ethan Lynn 2015-04-15 11:31:24 +08:00
parent 0185ad16f4
commit e7aeb452f4
10 changed files with 116 additions and 12 deletions

View File

@ -14,6 +14,7 @@
from neutronclient.common import exceptions
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.v2_0 import client as nc
from oslo_utils import netutils
from oslo_utils import uuidutils
from heat.common import exception
@ -172,3 +173,10 @@ class SubnetConstraint(constraints.BaseCustomConstraint):
neutron_client = client.client('neutron')
neutronV20.find_resourceid_by_name_or_id(
neutron_client, 'subnet', value)
class IPConstraint(constraints.BaseCustomConstraint):
def validate(self, value, context):
self._error_message = 'Invalid IP address'
return netutils.is_valid_ip(value)

View File

@ -201,7 +201,10 @@ class ElasticIpAssociation(resource.Resource):
EIP: properties.Schema(
properties.Schema.STRING,
_('EIP address to associate with instance.'),
update_allowed=True
update_allowed=True,
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
ALLOCATION_ID: properties.Schema(
properties.Schema.STRING,

View File

@ -78,13 +78,19 @@ class FloatingIP(neutron.NeutronResource):
FIXED_IP_ADDRESS: properties.Schema(
properties.Schema.STRING,
_('IP address to use if the port has multiple addresses.'),
update_allowed=True
update_allowed=True,
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
FLOATING_IP_ADDRESS: properties.Schema(
properties.Schema.STRING,
_('IP address of the floating IP. NOTE: The default policy '
'setting in Neutron restricts usage of this property to '
'administrative users only.'),
constraints=[
constraints.CustomConstraint('ip_addr')
],
support_status=support.SupportStatus(version='2015.2'),
),
}
@ -257,7 +263,10 @@ class FloatingIPAssociation(neutron.NeutronResource):
FIXED_IP_ADDRESS: properties.Schema(
properties.Schema.STRING,
_('IP address to use if the port has multiple addresses.'),
update_allowed=True
update_allowed=True,
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
}

View File

@ -278,7 +278,10 @@ class Pool(neutron.NeutronResource):
),
VIP_ADDRESS: properties.Schema(
properties.Schema.STRING,
_('IP address of the vip.')
_('IP address of the vip.'),
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
VIP_CONNECTION_LIMIT: properties.Schema(
properties.Schema.INTEGER,
@ -547,7 +550,10 @@ class PoolMember(neutron.NeutronResource):
ADDRESS: properties.Schema(
properties.Schema.STRING,
_('IP address of the pool member on the pool network.'),
required=True
required=True,
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
PROTOCOL_PORT: properties.Schema(
properties.Schema.INTEGER,

View File

@ -132,7 +132,10 @@ class Port(neutron.NeutronResource):
),
FIXED_IP_IP_ADDRESS: properties.Schema(
properties.Schema.STRING,
_('IP address desired in the subnet for this port.')
_('IP address desired in the subnet for this port.'),
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
},
),
@ -166,7 +169,10 @@ class Port(neutron.NeutronResource):
ALLOWED_ADDRESS_PAIR_IP_ADDRESS: properties.Schema(
properties.Schema.STRING,
_('IP address to allow through this port.'),
required=True
required=True,
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
},
)

View File

@ -11,6 +11,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import netutils
from heat.common import exception
from heat.common.i18n import _
from heat.engine import attributes
@ -130,11 +132,17 @@ class Subnet(neutron.NeutronResource):
schema={
ALLOCATION_POOL_START: properties.Schema(
properties.Schema.STRING,
required=True
required=True,
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
ALLOCATION_POOL_END: properties.Schema(
properties.Schema.STRING,
required=True
required=True,
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
},
)
@ -155,7 +163,10 @@ class Subnet(neutron.NeutronResource):
),
ROUTE_NEXTHOP: properties.Schema(
properties.Schema.STRING,
required=True
required=True,
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
},
),
@ -247,6 +258,13 @@ class Subnet(neutron.NeutronResource):
'they must be equal.')
raise exception.StackValidationFailed(message=msg)
gateway_ip = self.properties.get(self.GATEWAY_IP)
if (gateway_ip and gateway_ip not in ['~', ''] and
not netutils.is_valid_ip(gateway_ip)):
msg = (_('Gateway IP address "%(gateway)" is in '
'invalid format.'), gateway_ip)
raise exception.StackValidationFailed(message=msg)
def handle_create(self):
props = self.prepare_properties(
self.properties,

View File

@ -339,7 +339,10 @@ class Server(stack_user.StackUser):
NETWORK_FIXED_IP: properties.Schema(
properties.Schema.STRING,
_('Fixed IP address to specify for the port '
'created on the requested network.')
'created on the requested network.'),
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
NETWORK_PORT: properties.Schema(
properties.Schema.STRING,

View File

@ -149,7 +149,10 @@ class OSDBInstance(resource.Resource):
),
V4_FIXED_IP: properties.Schema(
properties.Schema.STRING,
_('Fixed IPv4 address for this NIC.')
_('Fixed IPv4 address for this NIC.'),
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
},
),

View File

@ -182,3 +182,50 @@ class NeutronConstraintsValidate(common.HeatTestCase):
self.assertFalse(constraint.validate("bar", ctx))
mock_find.assert_has_calls([mock.call(nc, self.resource_type, 'foo'),
mock.call(nc, self.resource_type, 'bar')])
class TestIPConstraint(common.HeatTestCase):
def setUp(self):
super(TestIPConstraint, self).setUp()
self.constraint = neutron.IPConstraint()
def test_validate_ipv4_format(self):
validate_format = [
'1.1.1.1',
'1.0.1.1',
'255.255.255.255'
]
for ip in validate_format:
self.assertTrue(self.constraint.validate(ip, None))
def test_invalidate_ipv4_format(self):
invalidate_format = [
'1.1.1.',
'1.1.1.256',
'invalidate format',
'1.a.1.1'
]
for ip in invalidate_format:
self.assertFalse(self.constraint.validate(ip, None))
def test_validate_ipv6_format(self):
validate_format = [
'2002:2002::20c:29ff:fe7d:811a',
'::1',
'2002::',
'2002::1',
]
for ip in validate_format:
self.assertTrue(self.constraint.validate(ip, None))
def test_invalidate_ipv6_format(self):
invalidate_format = [
'2002::2001::1',
'2002::g',
'invalidate format',
'2001::0::',
'20c:29ff:fe7d:811a'
]
for ip in invalidate_format:
self.assertFalse(self.constraint.validate(ip, None))

View File

@ -72,6 +72,7 @@ heat.constraints =
cinder.vtype = heat.engine.clients.os.cinder:VolumeTypeConstraint
sahara.image = heat.engine.clients.os.sahara:ImageConstraint
trove.flavor = heat.engine.clients.os.trove:FlavorConstraint
ip_addr = heat.engine.clients.os.neutron:IPConstraint
heat.stack_lifecycle_plugins =