Implement IP address allocation.
This fixes bug 1008029. The allocation is done as follows (according to the value of port['fixed_ips']): - api_router.ATTR_NOT_SPECIFIED - Quantum will generate an IP address. If a v4 subnet is defined then a v4 address will be generated. If a v6 subnet is defined then a v6 address will be generated. If both are defined then both v4 and v6 addresses will be generated. - user configuration which may contain a list of the following: - ip_address - the specific IP address will be generated - subnet_id - an IP address from the subnet will be generated Change-Id: I3bb1b83b8824364b37dbecfa140331c4a1fd2762
This commit is contained in:
parent
94298783e2
commit
80b9837857
@ -30,7 +30,8 @@ FAULT_MAP = {exceptions.NotFound: webob.exc.HTTPNotFound,
|
|||||||
exceptions.InUse: webob.exc.HTTPConflict,
|
exceptions.InUse: webob.exc.HTTPConflict,
|
||||||
exceptions.MacAddressGenerationFailure:
|
exceptions.MacAddressGenerationFailure:
|
||||||
webob.exc.HTTPServiceUnavailable,
|
webob.exc.HTTPServiceUnavailable,
|
||||||
exceptions.StateInvalid: webob.exc.HTTPBadRequest}
|
exceptions.StateInvalid: webob.exc.HTTPBadRequest,
|
||||||
|
exceptions.InvalidInput: webob.exc.HTTPBadRequest}
|
||||||
|
|
||||||
|
|
||||||
def fields(request):
|
def fields(request):
|
||||||
|
@ -66,10 +66,8 @@ RESOURCE_ATTRIBUTE_MAP = {
|
|||||||
'default': True},
|
'default': True},
|
||||||
'mac_address': {'allow_post': True, 'allow_put': False,
|
'mac_address': {'allow_post': True, 'allow_put': False,
|
||||||
'default': ATTR_NOT_SPECIFIED},
|
'default': ATTR_NOT_SPECIFIED},
|
||||||
'fixed_ips_v4': {'allow_post': True, 'allow_put': True,
|
'fixed_ips': {'allow_post': True, 'allow_put': True,
|
||||||
'default': ATTR_NOT_SPECIFIED},
|
'default': ATTR_NOT_SPECIFIED},
|
||||||
'fixed_ips_v6': {'allow_post': True, 'allow_put': True,
|
|
||||||
'default': ATTR_NOT_SPECIFIED},
|
|
||||||
'host_routes': {'allow_post': True, 'allow_put': True,
|
'host_routes': {'allow_post': True, 'allow_put': True,
|
||||||
'default': ATTR_NOT_SPECIFIED},
|
'default': ATTR_NOT_SPECIFIED},
|
||||||
'device_id': {'allow_post': True, 'allow_put': True, 'default': ''},
|
'device_id': {'allow_post': True, 'allow_put': True, 'default': ''},
|
||||||
|
@ -84,6 +84,11 @@ class NetworkInUse(InUse):
|
|||||||
"There is one or more attachments plugged into its ports.")
|
"There is one or more attachments plugged into its ports.")
|
||||||
|
|
||||||
|
|
||||||
|
class SubnetInUse(InUse):
|
||||||
|
message = _("Unable to complete operation on subnet %(subnet_id)s. "
|
||||||
|
"There is used by one or more ports.")
|
||||||
|
|
||||||
|
|
||||||
class PortInUse(InUse):
|
class PortInUse(InUse):
|
||||||
message = _("Unable to complete operation on port %(port_id)s "
|
message = _("Unable to complete operation on port %(port_id)s "
|
||||||
"for network %(net_id)s. The attachment '%(att_id)s"
|
"for network %(net_id)s. The attachment '%(att_id)s"
|
||||||
@ -95,6 +100,11 @@ class MacAddressInUse(InUse):
|
|||||||
"The mac address %(mac)s is in use.")
|
"The mac address %(mac)s is in use.")
|
||||||
|
|
||||||
|
|
||||||
|
class IpAddressInUse(InUse):
|
||||||
|
message = _("Unable to complete operation for network %(net_id)s. "
|
||||||
|
"The IP address %(ip_address)s is in use.")
|
||||||
|
|
||||||
|
|
||||||
class AlreadyAttached(QuantumException):
|
class AlreadyAttached(QuantumException):
|
||||||
message = _("Unable to plug the attachment %(att_id)s into port "
|
message = _("Unable to plug the attachment %(att_id)s into port "
|
||||||
"%(port_id)s for network %(net_id)s. The attachment is "
|
"%(port_id)s for network %(net_id)s. The attachment is "
|
||||||
@ -109,6 +119,10 @@ class Invalid(Error):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidInput(QuantumException):
|
||||||
|
message = _("Invalid input for operation: %(error_message)s.")
|
||||||
|
|
||||||
|
|
||||||
class InvalidContentType(Invalid):
|
class InvalidContentType(Invalid):
|
||||||
message = _("Invalid content type %(content_type)s.")
|
message = _("Invalid content type %(content_type)s.")
|
||||||
|
|
||||||
@ -124,3 +138,7 @@ class FixedIPNotAvailable(QuantumException):
|
|||||||
|
|
||||||
class MacAddressGenerationFailure(QuantumException):
|
class MacAddressGenerationFailure(QuantumException):
|
||||||
message = _("Unable to generate unique mac on network %(net_id)s.")
|
message = _("Unable to generate unique mac on network %(net_id)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class IpAddressGenerationFailure(QuantumException):
|
||||||
|
message = _("No more IP addresses available on network %(net_id)s.")
|
||||||
|
@ -162,6 +162,298 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _recycle_ip(context, network_id, subnet_id, port_id, ip_address):
|
||||||
|
"""Return an IP address to the pool of free IP's on the network
|
||||||
|
subnet.
|
||||||
|
"""
|
||||||
|
range_qry = context.session.query(models_v2.IPAllocationRange)
|
||||||
|
# Two requests will be done on the database. The first will be to
|
||||||
|
# search if an entry starts with ip_address + 1 (r1). The second
|
||||||
|
# will be to see if an entry ends with ip_address -1 (r2).
|
||||||
|
# If 1 of the above holds true then the specific entry will be
|
||||||
|
# modified. If both hold true then the two ranges will be merged.
|
||||||
|
# If there are no entries then a single entry will be added.
|
||||||
|
ip_first = str(netaddr.IPAddress(ip_address) + 1)
|
||||||
|
ip_last = str(netaddr.IPAddress(ip_address) - 1)
|
||||||
|
LOG.debug("Recycle %s", ip_address)
|
||||||
|
|
||||||
|
try:
|
||||||
|
r1 = range_qry.filter_by(subnet_id=subnet_id,
|
||||||
|
first_ip=ip_first).one()
|
||||||
|
LOG.debug("Recycle: first match for %s-%s", r1['first_ip'],
|
||||||
|
r1['last_ip'])
|
||||||
|
except exc.NoResultFound:
|
||||||
|
r1 = []
|
||||||
|
try:
|
||||||
|
r2 = range_qry.filter_by(subnet_id=subnet_id,
|
||||||
|
last_ip=ip_last).one()
|
||||||
|
LOG.debug("Recycle: last match for %s-%s", r2['first_ip'],
|
||||||
|
r2['last_ip'])
|
||||||
|
except exc.NoResultFound:
|
||||||
|
r2 = []
|
||||||
|
|
||||||
|
if r1 and r2:
|
||||||
|
# Merge the two ranges
|
||||||
|
ip_range = models_v2.IPAllocationRange(subnet_id=subnet_id,
|
||||||
|
first_ip=r2['first_ip'],
|
||||||
|
last_ip=r1['last_ip'])
|
||||||
|
context.session.add(ip_range)
|
||||||
|
LOG.debug("Recycle: merged %s-%s and %s-%s", r2['first_ip'],
|
||||||
|
r2['last_ip'], r1['first_ip'], r1['last_ip'])
|
||||||
|
context.session.delete(r1)
|
||||||
|
context.session.delete(r2)
|
||||||
|
elif r1:
|
||||||
|
# Update the range with matched first IP
|
||||||
|
r1['first_ip'] = ip_address
|
||||||
|
LOG.debug("Recycle: updated first %s-%s", r1['first_ip'],
|
||||||
|
r1['last_ip'])
|
||||||
|
elif r2:
|
||||||
|
# Update the range with matched last IP
|
||||||
|
r2['last_ip'] = ip_address
|
||||||
|
LOG.debug("Recycle: updated last %s-%s", r2['first_ip'],
|
||||||
|
r2['last_ip'])
|
||||||
|
else:
|
||||||
|
# Create a new range
|
||||||
|
ip_range = models_v2.IPAllocationRange(subnet_id=subnet_id,
|
||||||
|
first_ip=ip_address,
|
||||||
|
last_ip=ip_address)
|
||||||
|
context.session.add(ip_range)
|
||||||
|
LOG.debug("Recycle: created new %s-%s", ip_address, ip_address)
|
||||||
|
|
||||||
|
# Delete the IP address from the IPAllocate table
|
||||||
|
LOG.debug("Delete allocated IP %s (%s/%s/%s)", ip_address,
|
||||||
|
network_id, subnet_id, port_id)
|
||||||
|
alloc_qry = context.session.query(models_v2.IPAllocation)
|
||||||
|
allocated = alloc_qry.filter_by(network_id=network_id,
|
||||||
|
port_id=port_id,
|
||||||
|
ip_address=ip_address,
|
||||||
|
subnet_id=subnet_id).delete()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_ip(context, network_id, subnets):
|
||||||
|
"""Generate an IP address.
|
||||||
|
|
||||||
|
The IP address will be generated from one of the subnets defined on
|
||||||
|
the network.
|
||||||
|
"""
|
||||||
|
range_qry = context.session.query(models_v2.IPAllocationRange)
|
||||||
|
for subnet in subnets:
|
||||||
|
range = range_qry.filter_by(subnet_id=subnet['id']).first()
|
||||||
|
if not range:
|
||||||
|
LOG.debug("All IP's from subnet %s (%s) allocated",
|
||||||
|
subnet['id'], subnet['cidr'])
|
||||||
|
continue
|
||||||
|
ip_address = range['first_ip']
|
||||||
|
LOG.debug("Allocated IP - %s from %s to %s", ip_address,
|
||||||
|
range['first_ip'], range['last_ip'])
|
||||||
|
if range['first_ip'] == range['last_ip']:
|
||||||
|
# No more free indices on subnet => delete
|
||||||
|
LOG.debug("No more free IP's in slice. Deleting allocation "
|
||||||
|
"pool.")
|
||||||
|
context.session.delete(range)
|
||||||
|
else:
|
||||||
|
# increment the first free
|
||||||
|
range['first_ip'] = str(netaddr.IPAddress(ip_address) + 1)
|
||||||
|
return {'ip_address': ip_address, 'subnet_id': subnet['id']}
|
||||||
|
raise q_exc.IpAddressGenerationFailure(net_id=network_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _allocate_specific_ip(context, subnet_id, ip_address):
|
||||||
|
"""Allocate a specific IP address on the subnet."""
|
||||||
|
ip = int(netaddr.IPAddress(ip_address))
|
||||||
|
range_qry = context.session.query(models_v2.IPAllocationRange)
|
||||||
|
ranges = range_qry.filter_by(subnet_id=subnet_id).all()
|
||||||
|
for range in ranges:
|
||||||
|
first = int(netaddr.IPAddress(range['first_ip']))
|
||||||
|
last = int(netaddr.IPAddress(range['last_ip']))
|
||||||
|
if first <= ip <= last:
|
||||||
|
if first == last:
|
||||||
|
context.session.delete(range)
|
||||||
|
return
|
||||||
|
elif first == ip:
|
||||||
|
range['first_ip'] = str(netaddr.IPAddress(ip_address) + 1)
|
||||||
|
return
|
||||||
|
elif last == ip:
|
||||||
|
range['last_ip'] = str(netaddr.IPAddress(ip_address) - 1)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Split into two ranges
|
||||||
|
new_first = str(netaddr.IPAddress(ip_address) + 1)
|
||||||
|
new_last = range['last_ip']
|
||||||
|
range['last_ip'] = str(netaddr.IPAddress(ip_address) - 1)
|
||||||
|
ip_range = models_v2.IPAllocationRange(subnet_id=subnet_id,
|
||||||
|
first_ip=new_first,
|
||||||
|
last_ip=new_last)
|
||||||
|
context.session.add(ip_range)
|
||||||
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_unique_ip(context, network_id, subnet_id, ip_address):
|
||||||
|
"""Validate that the IP address on the subnet is not in use."""
|
||||||
|
ip_qry = context.session.query(models_v2.IPAllocation)
|
||||||
|
try:
|
||||||
|
ip_qry.filter_by(network_id=network_id,
|
||||||
|
subnet_id=subnet_id,
|
||||||
|
ip_address=ip_address).one()
|
||||||
|
except exc.NoResultFound:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_subnet_ip(cidr, ip_address):
|
||||||
|
"""Validate that the IP address is on the subnet."""
|
||||||
|
ip = netaddr.IPAddress(ip_address)
|
||||||
|
net = netaddr.IPNetwork(cidr)
|
||||||
|
# Check that the IP is valid on subnet. This cannot be the
|
||||||
|
# network or the broadcast address
|
||||||
|
if (ip != net.network and
|
||||||
|
ip != net.broadcast and
|
||||||
|
net.netmask & ip == net.ip):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _test_fixed_ips_for_port(self, context, network_id, fixed_ips):
|
||||||
|
"""Test fixed IPs for port.
|
||||||
|
|
||||||
|
Check that configured subnets are valid prior to allocating any
|
||||||
|
IPs. Include the subnet_id in the result if only an IP address is
|
||||||
|
configured.
|
||||||
|
|
||||||
|
:raises: InvalidInput, IpAddressInUse
|
||||||
|
"""
|
||||||
|
fixed_ip_set = []
|
||||||
|
for fixed in fixed_ips:
|
||||||
|
found = False
|
||||||
|
if 'subnet_id' not in fixed:
|
||||||
|
if 'ip_address' not in fixed:
|
||||||
|
msg = _('IP allocation requires subnet_id or ip_address')
|
||||||
|
raise q_exc.InvalidInput(error_message=msg)
|
||||||
|
|
||||||
|
filter = {'network_id': [network_id]}
|
||||||
|
subnets = self.get_subnets(context, filters=filter)
|
||||||
|
for subnet in subnets:
|
||||||
|
if QuantumDbPluginV2._check_subnet_ip(subnet['cidr'],
|
||||||
|
fixed['ip_address']):
|
||||||
|
found = True
|
||||||
|
subnet_id = subnet['id']
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
msg = _('IP address %s is not a valid IP for the defined '
|
||||||
|
'networks subnets') % fixed['ip_address']
|
||||||
|
raise q_exc.InvalidInput(error_message=msg)
|
||||||
|
else:
|
||||||
|
subnet = self._get_subnet(context, fixed['subnet_id'])
|
||||||
|
subnet_id = subnet['id']
|
||||||
|
|
||||||
|
if 'ip_address' in fixed:
|
||||||
|
# Ensure that the IP's are unique
|
||||||
|
if not QuantumDbPluginV2._check_unique_ip(context, network_id,
|
||||||
|
subnet_id,
|
||||||
|
fixed['ip_address']):
|
||||||
|
raise q_exc.IpAddressInUse(net_id=network_id,
|
||||||
|
ip_address=fixed['ip_address'])
|
||||||
|
|
||||||
|
# Ensure that the IP is valid on the subnet
|
||||||
|
if (not found and
|
||||||
|
not QuantumDbPluginV2._check_subnet_ip(
|
||||||
|
subnet['cidr'], fixed['ip_address'])):
|
||||||
|
msg = _('IP address %s is not a valid IP for the defined '
|
||||||
|
'subnet') % fixed['ip_address']
|
||||||
|
raise q_exc.InvalidInput(error_message=msg)
|
||||||
|
|
||||||
|
fixed_ip_set.append({'subnet_id': subnet_id,
|
||||||
|
'ip_address': fixed['ip_address']})
|
||||||
|
else:
|
||||||
|
fixed_ip_set.append({'subnet_id': subnet_id})
|
||||||
|
return fixed_ip_set
|
||||||
|
|
||||||
|
def _allocate_fixed_ips(self, context, network, fixed_ips):
|
||||||
|
"""Allocate IP addresses according to the configured fixed_ips."""
|
||||||
|
ips = []
|
||||||
|
for fixed in fixed_ips:
|
||||||
|
if 'ip_address' in fixed:
|
||||||
|
# Remove the IP address from the allocation pool
|
||||||
|
QuantumDbPluginV2._allocate_specific_ip(
|
||||||
|
context, fixed['subnet_id'], fixed['ip_address'])
|
||||||
|
ips.append({'ip_address': fixed['ip_address'],
|
||||||
|
'subnet_id': fixed['subnet_id']})
|
||||||
|
# Only subnet ID is specified => need to generate IP
|
||||||
|
# from subnet
|
||||||
|
else:
|
||||||
|
subnets = [self._get_subnet(context, fixed['subnet_id'])]
|
||||||
|
# IP address allocation
|
||||||
|
result = self._generate_ip(context, network, subnets)
|
||||||
|
ips.append({'ip_address': result['ip_address'],
|
||||||
|
'subnet_id': result['subnet_id']})
|
||||||
|
return ips
|
||||||
|
|
||||||
|
def _update_ips_for_port(self, context, network_id, port_id, original_ips,
|
||||||
|
new_ips):
|
||||||
|
"""Add or remove IPs from the port."""
|
||||||
|
ips = []
|
||||||
|
# Remove all of the intersecting elements
|
||||||
|
for original_ip in original_ips[:]:
|
||||||
|
for new_ip in new_ips[:]:
|
||||||
|
if 'ip_address' in new_ip:
|
||||||
|
if (original_ip['ip_address'] == new_ip['ip_address']
|
||||||
|
and
|
||||||
|
original_ip['subnet_id'] == new_ip['subnet_id']):
|
||||||
|
original_ips.remove(original_ip)
|
||||||
|
new_ips.remove(new_ip)
|
||||||
|
|
||||||
|
# Check if the IP's to add are OK
|
||||||
|
to_add = self._test_fixed_ips_for_port(context, network_id, new_ips)
|
||||||
|
for ip in original_ips:
|
||||||
|
LOG.debug("Port update. Deleting %s", ip)
|
||||||
|
QuantumDbPluginV2._recycle_ip(context,
|
||||||
|
network_id=network_id,
|
||||||
|
subnet_id=ip['subnet_id'],
|
||||||
|
ip_address=ip['ip_address'],
|
||||||
|
port_id=port_id)
|
||||||
|
|
||||||
|
if to_add:
|
||||||
|
LOG.debug("Port update. Adding %s", to_add)
|
||||||
|
network = self._get_network(context, network_id)
|
||||||
|
ips = self._allocate_fixed_ips(context, network, to_add)
|
||||||
|
return ips
|
||||||
|
|
||||||
|
def _allocate_ips_for_port(self, context, network, port):
|
||||||
|
"""Allocate IP addresses for the port.
|
||||||
|
|
||||||
|
If port['fixed_ips'] is set to 'ATTR_NOT_SPECIFIED', allocate IP
|
||||||
|
addresses for the port. If port['fixed_ips'] contains an IP address or
|
||||||
|
a subnet_id then allocate an IP address accordingly.
|
||||||
|
"""
|
||||||
|
p = port['port']
|
||||||
|
ips = []
|
||||||
|
|
||||||
|
fixed_configured = (p['fixed_ips'] != api_router.ATTR_NOT_SPECIFIED)
|
||||||
|
if fixed_configured:
|
||||||
|
configured_ips = self._test_fixed_ips_for_port(context,
|
||||||
|
p["network_id"],
|
||||||
|
p['fixed_ips'])
|
||||||
|
ips = self._allocate_fixed_ips(context, network, configured_ips)
|
||||||
|
else:
|
||||||
|
filter = {'network_id': [p['network_id']]}
|
||||||
|
subnets = self.get_subnets(context, filters=filter)
|
||||||
|
# Split into v4 and v6 subnets
|
||||||
|
v4 = []
|
||||||
|
v6 = []
|
||||||
|
for subnet in subnets:
|
||||||
|
if subnet['ip_version'] == 4:
|
||||||
|
v4.append(subnet)
|
||||||
|
else:
|
||||||
|
v6.append(subnet)
|
||||||
|
version_subnets = [v4, v6]
|
||||||
|
for subnets in version_subnets:
|
||||||
|
if subnets:
|
||||||
|
result = QuantumDbPluginV2._generate_ip(context, network,
|
||||||
|
subnets)
|
||||||
|
ips.append({'ip_address': result['ip_address'],
|
||||||
|
'subnet_id': result['subnet_id']})
|
||||||
|
return ips
|
||||||
|
|
||||||
def _make_network_dict(self, network, fields=None):
|
def _make_network_dict(self, network, fields=None):
|
||||||
res = {'id': network['id'],
|
res = {'id': network['id'],
|
||||||
'name': network['name'],
|
'name': network['name'],
|
||||||
@ -188,7 +480,9 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
"mac_address": port["mac_address"],
|
"mac_address": port["mac_address"],
|
||||||
"admin_state_up": port["admin_state_up"],
|
"admin_state_up": port["admin_state_up"],
|
||||||
"status": port["status"],
|
"status": port["status"],
|
||||||
"fixed_ips": [ip["address"] for ip in port["fixed_ips"]],
|
"fixed_ips": [{'subnet_id': ip["subnet_id"],
|
||||||
|
'ip_address': ip["ip_address"]}
|
||||||
|
for ip in port["fixed_ips"]],
|
||||||
"device_id": port["device_id"]}
|
"device_id": port["device_id"]}
|
||||||
return self._fields(res, fields)
|
return self._fields(res, fields)
|
||||||
|
|
||||||
@ -239,18 +533,45 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
def create_subnet(self, context, subnet):
|
def create_subnet(self, context, subnet):
|
||||||
s = subnet['subnet']
|
s = subnet['subnet']
|
||||||
|
|
||||||
|
net = netaddr.IPNetwork(s['cidr'])
|
||||||
if s['gateway_ip'] == api_router.ATTR_NOT_SPECIFIED:
|
if s['gateway_ip'] == api_router.ATTR_NOT_SPECIFIED:
|
||||||
net = netaddr.IPNetwork(s['cidr'])
|
|
||||||
s['gateway_ip'] = str(netaddr.IPAddress(net.first + 1))
|
s['gateway_ip'] = str(netaddr.IPAddress(net.first + 1))
|
||||||
|
|
||||||
|
ip = netaddr.IPAddress(s['gateway_ip'])
|
||||||
|
# Get the first and last indices for the subnet
|
||||||
|
ranges = []
|
||||||
|
# Gateway is the first address in the range
|
||||||
|
if ip == net.network + 1:
|
||||||
|
range = {'first': str(ip + 1),
|
||||||
|
'last': str(net.broadcast - 1)}
|
||||||
|
ranges.append(range)
|
||||||
|
# Gateway is the last address in the range
|
||||||
|
elif ip == net.broadcast - 1:
|
||||||
|
range = {'first': str(net.network + 1),
|
||||||
|
'last': str(ip - 1)}
|
||||||
|
ranges.append(range)
|
||||||
|
# Gateway is on IP in the subnet
|
||||||
|
else:
|
||||||
|
range = {'first': str(net.network + 1),
|
||||||
|
'last': str(ip - 1)}
|
||||||
|
ranges.append(range)
|
||||||
|
range = {'first': str(ip + 1),
|
||||||
|
'last': str(net.broadcast - 1)}
|
||||||
|
ranges.append(range)
|
||||||
with context.session.begin():
|
with context.session.begin():
|
||||||
network = self._get_network(context, s["network_id"])
|
network = self._get_network(context, s["network_id"])
|
||||||
subnet = models_v2.Subnet(network_id=s['network_id'],
|
subnet = models_v2.Subnet(network_id=s['network_id'],
|
||||||
ip_version=s['ip_version'],
|
ip_version=s['ip_version'],
|
||||||
cidr=s['cidr'],
|
cidr=s['cidr'],
|
||||||
gateway_ip=s['gateway_ip'])
|
gateway_ip=s['gateway_ip'])
|
||||||
|
|
||||||
context.session.add(subnet)
|
context.session.add(subnet)
|
||||||
|
|
||||||
|
with context.session.begin():
|
||||||
|
for range in ranges:
|
||||||
|
ip_range = models_v2.IPAllocationRange(subnet_id=subnet.id,
|
||||||
|
first_ip=range['first'],
|
||||||
|
last_ip=range['last'])
|
||||||
|
context.session.add(ip_range)
|
||||||
return self._make_subnet_dict(subnet)
|
return self._make_subnet_dict(subnet)
|
||||||
|
|
||||||
def update_subnet(self, context, id, subnet):
|
def update_subnet(self, context, id, subnet):
|
||||||
@ -263,10 +584,11 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
def delete_subnet(self, context, id):
|
def delete_subnet(self, context, id):
|
||||||
with context.session.begin():
|
with context.session.begin():
|
||||||
subnet = self._get_subnet(context, id)
|
subnet = self._get_subnet(context, id)
|
||||||
|
# Check if ports are using this subnet
|
||||||
allocations_qry = context.session.query(models_v2.IPAllocation)
|
allocated_qry = context.session.query(models_v2.IPAllocation)
|
||||||
allocations_qry.filter_by(subnet_id=id).delete()
|
allocated = allocated_qry.filter_by(port_id=id).all()
|
||||||
|
if allocated:
|
||||||
|
raise q_exc.SubnetInUse(subnet_id=id)
|
||||||
context.session.delete(subnet)
|
context.session.delete(subnet)
|
||||||
|
|
||||||
def get_subnet(self, context, id, fields=None, verbose=None):
|
def get_subnet(self, context, id, fields=None, verbose=None):
|
||||||
@ -301,6 +623,9 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
raise q_exc.MacAddressInUse(net_id=p["network_id"],
|
raise q_exc.MacAddressInUse(net_id=p["network_id"],
|
||||||
mac=p['mac_address'])
|
mac=p['mac_address'])
|
||||||
|
|
||||||
|
# Returns the IP's for the port
|
||||||
|
ips = self._allocate_ips_for_port(context, network, port)
|
||||||
|
|
||||||
port = models_v2.Port(tenant_id=tenant_id,
|
port = models_v2.Port(tenant_id=tenant_id,
|
||||||
network_id=p['network_id'],
|
network_id=p['network_id'],
|
||||||
mac_address=p['mac_address'],
|
mac_address=p['mac_address'],
|
||||||
@ -309,26 +634,71 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
device_id=p['device_id'])
|
device_id=p['device_id'])
|
||||||
context.session.add(port)
|
context.session.add(port)
|
||||||
|
|
||||||
# TODO(anyone) ip allocation
|
# Update the allocated IP's
|
||||||
#for subnet in network["subnets"]:
|
if ips:
|
||||||
# pass
|
with context.session.begin():
|
||||||
|
for ip in ips:
|
||||||
|
LOG.debug("Allocated IP %s (%s/%s/%s)", ip['ip_address'],
|
||||||
|
port['network_id'], ip['subnet_id'], port.id)
|
||||||
|
allocated = models_v2.IPAllocation(
|
||||||
|
network_id=port['network_id'],
|
||||||
|
port_id=port.id,
|
||||||
|
ip_address=ip['ip_address'],
|
||||||
|
subnet_id=ip['subnet_id'])
|
||||||
|
context.session.add(allocated)
|
||||||
|
|
||||||
return self._make_port_dict(port)
|
return self._make_port_dict(port)
|
||||||
|
|
||||||
def update_port(self, context, id, port):
|
def update_port(self, context, id, port):
|
||||||
p = port['port']
|
p = port['port']
|
||||||
|
|
||||||
with context.session.begin():
|
with context.session.begin():
|
||||||
port = self._get_port(context, id)
|
port = self._get_port(context, id)
|
||||||
|
# Check if the IPs need to be updated
|
||||||
|
if 'fixed_ips' in p:
|
||||||
|
original = self._make_port_dict(port)
|
||||||
|
ips = self._update_ips_for_port(context,
|
||||||
|
port["network_id"],
|
||||||
|
id,
|
||||||
|
original["fixed_ips"],
|
||||||
|
p['fixed_ips'])
|
||||||
|
# 'fixed_ip's not part of DB so it is deleted
|
||||||
|
del p['fixed_ips']
|
||||||
|
|
||||||
|
# Update ips if necessary
|
||||||
|
for ip in ips:
|
||||||
|
allocated = models_v2.IPAllocation(
|
||||||
|
network_id=port['network_id'], port_id=port.id,
|
||||||
|
ip_address=ip['ip_address'], subnet_id=ip['subnet_id'])
|
||||||
|
context.session.add(allocated)
|
||||||
|
|
||||||
port.update(p)
|
port.update(p)
|
||||||
|
|
||||||
return self._make_port_dict(port)
|
return self._make_port_dict(port)
|
||||||
|
|
||||||
def delete_port(self, context, id):
|
def delete_port(self, context, id):
|
||||||
with context.session.begin():
|
with context.session.begin():
|
||||||
port = self._get_port(context, id)
|
port = self._get_port(context, id)
|
||||||
|
|
||||||
allocations_qry = context.session.query(models_v2.IPAllocation)
|
allocated_qry = context.session.query(models_v2.IPAllocation)
|
||||||
allocations_qry.filter_by(port_id=id).delete()
|
# recycle all of the IP's
|
||||||
|
# NOTE(garyk) this may be have to be addressed differently when
|
||||||
|
# working with a DHCP server.
|
||||||
|
allocated = allocated_qry.filter_by(port_id=id).all()
|
||||||
|
if allocated:
|
||||||
|
for a in allocated:
|
||||||
|
# Gateway address will not be recycled
|
||||||
|
subnet = self._get_subnet(context, a['subnet_id'])
|
||||||
|
if a['ip_address'] == subnet['gateway_ip']:
|
||||||
|
LOG.debug("Gateway address (%s/%s) is not recycled",
|
||||||
|
a['ip_address'], a['subnet_id'])
|
||||||
|
continue
|
||||||
|
|
||||||
|
QuantumDbPluginV2._recycle_ip(context,
|
||||||
|
network_id=a['network_id'],
|
||||||
|
subnet_id=a['subnet_id'],
|
||||||
|
ip_address=a['ip_address'],
|
||||||
|
port_id=id)
|
||||||
context.session.delete(port)
|
context.session.delete(port)
|
||||||
|
|
||||||
def get_port(self, context, id, fields=None, verbose=None):
|
def get_port(self, context, id, fields=None, verbose=None):
|
||||||
@ -336,7 +706,35 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
return self._make_port_dict(port, fields)
|
return self._make_port_dict(port, fields)
|
||||||
|
|
||||||
def get_ports(self, context, filters=None, fields=None, verbose=None):
|
def get_ports(self, context, filters=None, fields=None, verbose=None):
|
||||||
return self._get_collection(context, models_v2.Port,
|
fixed_ips = filters.pop('fixed_ips', [])
|
||||||
self._make_port_dict,
|
ports = self._get_collection(context, models_v2.Port,
|
||||||
filters=filters, fields=fields,
|
self._make_port_dict,
|
||||||
verbose=verbose)
|
filters=filters, fields=fields,
|
||||||
|
verbose=verbose)
|
||||||
|
if ports and fixed_ips:
|
||||||
|
filtered_ports = []
|
||||||
|
for port in ports:
|
||||||
|
if port['fixed_ips']:
|
||||||
|
ips = port['fixed_ips']
|
||||||
|
for fixed in fixed_ips:
|
||||||
|
found = False
|
||||||
|
# Convert to dictionary (deserialize)
|
||||||
|
fixed = eval(fixed)
|
||||||
|
for ip in ips:
|
||||||
|
if 'ip_address' in fixed and 'subnet_id' in fixed:
|
||||||
|
if (ip['ip_address'] == fixed['ip_address'] and
|
||||||
|
ip['subnet_id'] == fixed['subnet_id']):
|
||||||
|
found = True
|
||||||
|
elif 'ip_address' in fixed:
|
||||||
|
if ip['ip_address'] == fixed['ip_address']:
|
||||||
|
found = True
|
||||||
|
elif 'subnet_id' in fixed:
|
||||||
|
if ip['subnet_id'] == fixed['subnet_id']:
|
||||||
|
found = True
|
||||||
|
if found:
|
||||||
|
filtered_ports.append(port)
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
return filtered_ports
|
||||||
|
return ports
|
||||||
|
@ -25,22 +25,39 @@ class HasTenant(object):
|
|||||||
tenant_id = sa.Column(sa.String(255))
|
tenant_id = sa.Column(sa.String(255))
|
||||||
|
|
||||||
|
|
||||||
class IPAllocation(model_base.BASEV2):
|
class IPAllocationRange(model_base.BASEV2):
|
||||||
"""Internal representation of a IP address allocation in a Quantum
|
"""Internal representation of a free IP address range in a Quantum
|
||||||
subnet
|
subnet. The range of available ips is [first_ip..last_ip]. The
|
||||||
|
allocation retrieves the first entry from the range. If the first
|
||||||
|
entry is equal to the last entry then this row will be deleted.
|
||||||
|
Recycling ips involves appending to existing ranges. This is
|
||||||
|
only done if the range is contiguous. If not, the first_ip will be
|
||||||
|
the same as the last_ip. When adjacent ips are recycled the ranges
|
||||||
|
will be merged.
|
||||||
"""
|
"""
|
||||||
port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id'))
|
|
||||||
address = sa.Column(sa.String(16), nullable=False, primary_key=True)
|
|
||||||
subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id'),
|
subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id'),
|
||||||
primary_key=True)
|
nullable=True)
|
||||||
allocated = sa.Column(sa.Boolean(), nullable=False)
|
first_ip = sa.Column(sa.String(64), nullable=False)
|
||||||
|
last_ip = sa.Column(sa.String(64), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class IPAllocation(model_base.BASEV2):
|
||||||
|
"""Internal representation of allocated IP addresses in a Quantum subnet.
|
||||||
|
"""
|
||||||
|
port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id'),
|
||||||
|
nullable=False, primary_key=True)
|
||||||
|
ip_address = sa.Column(sa.String(64), nullable=False, primary_key=True)
|
||||||
|
subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id'),
|
||||||
|
nullable=False, primary_key=True)
|
||||||
|
network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id"),
|
||||||
|
nullable=False, primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class Port(model_base.BASEV2, HasTenant):
|
class Port(model_base.BASEV2, HasTenant):
|
||||||
"""Represents a port on a quantum v2 network"""
|
"""Represents a port on a quantum v2 network."""
|
||||||
network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id"),
|
network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id"),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
fixed_ips = orm.relationship(IPAllocation, backref='ports')
|
fixed_ips = orm.relationship(IPAllocation, backref='ports', lazy="dynamic")
|
||||||
mac_address = sa.Column(sa.String(32), nullable=False)
|
mac_address = sa.Column(sa.String(32), nullable=False)
|
||||||
admin_state_up = sa.Column(sa.Boolean(), nullable=False)
|
admin_state_up = sa.Column(sa.Boolean(), nullable=False)
|
||||||
status = sa.Column(sa.String(16), nullable=False)
|
status = sa.Column(sa.String(16), nullable=False)
|
||||||
@ -48,14 +65,15 @@ class Port(model_base.BASEV2, HasTenant):
|
|||||||
|
|
||||||
|
|
||||||
class Subnet(model_base.BASEV2):
|
class Subnet(model_base.BASEV2):
|
||||||
"""Represents a quantum subnet"""
|
"""Represents a quantum subnet.
|
||||||
|
|
||||||
|
When a subnet is created the first and last entries will be created. These
|
||||||
|
are used for the IP allocation.
|
||||||
|
"""
|
||||||
network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id'))
|
network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id'))
|
||||||
allocations = orm.relationship(IPAllocation,
|
|
||||||
backref=orm.backref('subnet',
|
|
||||||
uselist=False))
|
|
||||||
ip_version = sa.Column(sa.Integer, nullable=False)
|
ip_version = sa.Column(sa.Integer, nullable=False)
|
||||||
cidr = sa.Column(sa.String(64), nullable=False)
|
cidr = sa.Column(sa.String(64), nullable=False)
|
||||||
gateway_ip = sa.Column(sa.String(255))
|
gateway_ip = sa.Column(sa.String(64))
|
||||||
|
|
||||||
#TODO(danwent):
|
#TODO(danwent):
|
||||||
# - dns_namservers
|
# - dns_namservers
|
||||||
@ -64,7 +82,7 @@ class Subnet(model_base.BASEV2):
|
|||||||
|
|
||||||
|
|
||||||
class Network(model_base.BASEV2, HasTenant):
|
class Network(model_base.BASEV2, HasTenant):
|
||||||
"""Represents a v2 quantum network"""
|
"""Represents a v2 quantum network."""
|
||||||
name = sa.Column(sa.String(255))
|
name = sa.Column(sa.String(255))
|
||||||
ports = orm.relationship(Port, backref='networks')
|
ports = orm.relationship(Port, backref='networks')
|
||||||
subnets = orm.relationship(Subnet, backref='networks')
|
subnets = orm.relationship(Subnet, backref='networks')
|
||||||
|
@ -549,15 +549,12 @@ class JSONV2TestCase(APIv2TestCase):
|
|||||||
'admin_state_up': True}}
|
'admin_state_up': True}}
|
||||||
full_input = {'port': {'admin_state_up': True,
|
full_input = {'port': {'admin_state_up': True,
|
||||||
'mac_address': router.ATTR_NOT_SPECIFIED,
|
'mac_address': router.ATTR_NOT_SPECIFIED,
|
||||||
'fixed_ips_v4': router.ATTR_NOT_SPECIFIED,
|
'fixed_ips': router.ATTR_NOT_SPECIFIED,
|
||||||
'fixed_ips_v6': router.ATTR_NOT_SPECIFIED,
|
|
||||||
'host_routes': router.ATTR_NOT_SPECIFIED}}
|
'host_routes': router.ATTR_NOT_SPECIFIED}}
|
||||||
full_input['port'].update(initial_input['port'])
|
full_input['port'].update(initial_input['port'])
|
||||||
return_value = {'id': _uuid(), 'status': 'ACTIVE',
|
return_value = {'id': _uuid(), 'status': 'ACTIVE',
|
||||||
'admin_state_up': True,
|
'admin_state_up': True,
|
||||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
||||||
'fixed_ips_v4': ['10.0.0.0/24'],
|
|
||||||
'fixed_ips_v6': [],
|
|
||||||
'host_routes': [],
|
'host_routes': [],
|
||||||
'device_id': device_id}
|
'device_id': device_id}
|
||||||
return_value.update(initial_input['port'])
|
return_value.update(initial_input['port'])
|
||||||
|
@ -17,6 +17,7 @@ import contextlib
|
|||||||
import logging
|
import logging
|
||||||
import mock
|
import mock
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import quantum
|
import quantum
|
||||||
@ -110,10 +111,10 @@ class QuantumDbPluginV2TestCase(unittest.TestCase):
|
|||||||
network_req = self.new_create_request('networks', data, fmt)
|
network_req = self.new_create_request('networks', data, fmt)
|
||||||
return network_req.get_response(self.api)
|
return network_req.get_response(self.api)
|
||||||
|
|
||||||
def _create_subnet(self, fmt, net_id, gateway_ip, cidr):
|
def _create_subnet(self, fmt, net_id, gateway_ip, cidr, ip_version=4):
|
||||||
data = {'subnet': {'network_id': net_id,
|
data = {'subnet': {'network_id': net_id,
|
||||||
'cidr': cidr,
|
'cidr': cidr,
|
||||||
'ip_version': 4}}
|
'ip_version': ip_version}}
|
||||||
if gateway_ip:
|
if gateway_ip:
|
||||||
data['subnet']['gateway_ip'] = gateway_ip
|
data['subnet']['gateway_ip'] = gateway_ip
|
||||||
|
|
||||||
@ -125,17 +126,16 @@ class QuantumDbPluginV2TestCase(unittest.TestCase):
|
|||||||
content_type = 'application/' + fmt
|
content_type = 'application/' + fmt
|
||||||
data = {'port': {'network_id': net_id,
|
data = {'port': {'network_id': net_id,
|
||||||
'tenant_id': self._tenant_id}}
|
'tenant_id': self._tenant_id}}
|
||||||
for arg in ('admin_state_up', 'device_id', 'mac_address',
|
for arg in ('admin_state_up', 'device_id', 'mac_address', 'fixed_ips'):
|
||||||
'fixed_ips_v4', 'fixed_ips_v6'):
|
|
||||||
if arg in kwargs:
|
if arg in kwargs:
|
||||||
data['port'][arg] = kwargs[arg]
|
data['port'][arg] = kwargs[arg]
|
||||||
|
|
||||||
port_req = self.new_create_request('ports', data, fmt)
|
port_req = self.new_create_request('ports', data, fmt)
|
||||||
return port_req.get_response(self.api)
|
return port_req.get_response(self.api)
|
||||||
|
|
||||||
def _make_subnet(self, fmt, network, gateway, cidr):
|
def _make_subnet(self, fmt, network, gateway, cidr, ip_version=4):
|
||||||
res = self._create_subnet(fmt, network['network']['id'],
|
res = self._create_subnet(fmt, network['network']['id'],
|
||||||
gateway, cidr)
|
gateway, cidr, ip_version)
|
||||||
return self.deserialize(fmt, res)
|
return self.deserialize(fmt, res)
|
||||||
|
|
||||||
def _make_port(self, fmt, net_id, **kwargs):
|
def _make_port(self, fmt, net_id, **kwargs):
|
||||||
@ -175,6 +175,11 @@ class QuantumDbPluginV2TestCase(unittest.TestCase):
|
|||||||
port = self._make_port(fmt, net_id)
|
port = self._make_port(fmt, net_id)
|
||||||
yield port
|
yield port
|
||||||
self._delete('ports', port['port']['id'])
|
self._delete('ports', port['port']['id'])
|
||||||
|
else:
|
||||||
|
net_id = subnet['subnet']['network_id']
|
||||||
|
port = self._make_port(fmt, net_id)
|
||||||
|
yield port
|
||||||
|
self._delete('ports', port['port']['id'])
|
||||||
|
|
||||||
|
|
||||||
class TestV2HTTPResponse(QuantumDbPluginV2TestCase):
|
class TestV2HTTPResponse(QuantumDbPluginV2TestCase):
|
||||||
@ -222,6 +227,9 @@ class TestPortsV2(QuantumDbPluginV2TestCase):
|
|||||||
for k, v in keys:
|
for k, v in keys:
|
||||||
self.assertEquals(port['port'][k], v)
|
self.assertEquals(port['port'][k], v)
|
||||||
self.assertTrue('mac_address' in port['port'])
|
self.assertTrue('mac_address' in port['port'])
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
|
||||||
def test_list_ports(self):
|
def test_list_ports(self):
|
||||||
with contextlib.nested(self.port(), self.port()) as (port1, port2):
|
with contextlib.nested(self.port(), self.port()) as (port1, port2):
|
||||||
@ -263,6 +271,83 @@ class TestPortsV2(QuantumDbPluginV2TestCase):
|
|||||||
res = req.get_response(self.api)
|
res = req.get_response(self.api)
|
||||||
self.assertEquals(res.status_int, 409)
|
self.assertEquals(res.status_int, 409)
|
||||||
|
|
||||||
|
def test_update_port_delete_ip(self):
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
data = {'port': {'admin_state_up': False,
|
||||||
|
'fixed_ips': []}}
|
||||||
|
req = self.new_update_request('ports',
|
||||||
|
data, port['port']['id'])
|
||||||
|
res = self.deserialize('json', req.get_response(self.api))
|
||||||
|
self.assertEqual(res['port']['admin_state_up'],
|
||||||
|
data['port']['admin_state_up'])
|
||||||
|
self.assertEqual(res['port']['fixed_ips'],
|
||||||
|
data['port']['fixed_ips'])
|
||||||
|
|
||||||
|
def test_update_port_update_ip(self):
|
||||||
|
"""Test update of port IP.
|
||||||
|
|
||||||
|
Check that a configured IP 10.0.0.2 is replaced by 10.0.0.10.
|
||||||
|
"""
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
data = {'port': {'fixed_ips': [{'subnet_id':
|
||||||
|
subnet['subnet']['id'],
|
||||||
|
'ip_address': "10.0.0.10"}]}}
|
||||||
|
req = self.new_update_request('ports', data,
|
||||||
|
port['port']['id'])
|
||||||
|
res = self.deserialize('json', req.get_response(self.api))
|
||||||
|
ips = res['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.10')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
|
||||||
|
def test_update_port_update_ips(self):
|
||||||
|
"""Update IP and generate new IP on port.
|
||||||
|
|
||||||
|
Check a port update with the specified subnet_id's. A IP address
|
||||||
|
will be allocated for each subnet_id.
|
||||||
|
"""
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
data = {'port': {'admin_state_up': False,
|
||||||
|
'fixed_ips': [{'subnet_id':
|
||||||
|
subnet['subnet']['id']}]}}
|
||||||
|
req = self.new_update_request('ports', data,
|
||||||
|
port['port']['id'])
|
||||||
|
res = self.deserialize('json', req.get_response(self.api))
|
||||||
|
self.assertEqual(res['port']['admin_state_up'],
|
||||||
|
data['port']['admin_state_up'])
|
||||||
|
ips = res['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
|
||||||
|
def test_update_port_add_additional_ip(self):
|
||||||
|
"""Test update of port with additional IP."""
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
data = {'port': {'admin_state_up': False,
|
||||||
|
'fixed_ips': [{'subnet_id':
|
||||||
|
subnet['subnet']['id']},
|
||||||
|
{'subnet_id':
|
||||||
|
subnet['subnet']['id']}]}}
|
||||||
|
req = self.new_update_request('ports', data,
|
||||||
|
port['port']['id'])
|
||||||
|
res = self.deserialize('json', req.get_response(self.api))
|
||||||
|
self.assertEqual(res['port']['admin_state_up'],
|
||||||
|
data['port']['admin_state_up'])
|
||||||
|
ips = res['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 2)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
self.assertEquals(ips[1]['ip_address'], '10.0.0.3')
|
||||||
|
self.assertEquals(ips[1]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
|
||||||
def test_requested_duplicate_mac(self):
|
def test_requested_duplicate_mac(self):
|
||||||
fmt = 'json'
|
fmt = 'json'
|
||||||
with self.port() as port:
|
with self.port() as port:
|
||||||
@ -293,6 +378,249 @@ class TestPortsV2(QuantumDbPluginV2TestCase):
|
|||||||
res = self._create_port(fmt, net_id=net_id)
|
res = self._create_port(fmt, net_id=net_id)
|
||||||
self.assertEquals(res.status_int, 503)
|
self.assertEquals(res.status_int, 503)
|
||||||
|
|
||||||
|
def test_requested_duplicate_ip(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
# Check configuring of duplicate IP
|
||||||
|
kwargs = {"fixed_ips": [{'subnet_id': subnet['subnet']['id'],
|
||||||
|
'ip_address': ips[0]['ip_address']}]}
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port2 = self.deserialize(fmt, res)
|
||||||
|
self.assertEquals(res.status_int, 409)
|
||||||
|
|
||||||
|
def test_requested_subnet_delete(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
req = self.new_delete_request('subnet',
|
||||||
|
subnet['subnet']['id'])
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 404)
|
||||||
|
|
||||||
|
def test_requested_subnet_id(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
# Request a IP from specific subnet
|
||||||
|
kwargs = {"fixed_ips": [{'subnet_id': subnet['subnet']['id']}]}
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port2 = self.deserialize(fmt, res)
|
||||||
|
ips = port2['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.3')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
|
||||||
|
def test_requested_subnet_id_v4_and_v6(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
# Get a IPv4 and IPv6 address
|
||||||
|
net_id = subnet['subnet']['network_id']
|
||||||
|
res = self._create_subnet(fmt, net_id=net_id,
|
||||||
|
cidr='2607:f0d0:1002:51::0/124',
|
||||||
|
ip_version=6, gateway_ip=None)
|
||||||
|
subnet2 = self.deserialize(fmt, res)
|
||||||
|
kwargs = {"fixed_ips":
|
||||||
|
[{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet2['subnet']['id']}]}
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port3 = self.deserialize(fmt, res)
|
||||||
|
ips = port3['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 2)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
self.assertEquals(ips[1]['ip_address'], '2607:f0d0:1002:51::2')
|
||||||
|
self.assertEquals(ips[1]['subnet_id'], subnet2['subnet']['id'])
|
||||||
|
res = self._create_port(fmt, net_id=net_id)
|
||||||
|
port3 = self.deserialize(fmt, res)
|
||||||
|
# Check that a v4 and a v6 address are allocated
|
||||||
|
ips = port3['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 2)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.3')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
self.assertEquals(ips[1]['ip_address'], '2607:f0d0:1002:51::3')
|
||||||
|
self.assertEquals(ips[1]['subnet_id'], subnet2['subnet']['id'])
|
||||||
|
|
||||||
|
def test_range_allocation(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet(gateway='10.0.0.3',
|
||||||
|
cidr='10.0.0.0/29') as subnet:
|
||||||
|
kwargs = {"fixed_ips":
|
||||||
|
[{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet['subnet']['id']}]}
|
||||||
|
net_id = subnet['subnet']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port = self.deserialize(fmt, res)
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 5)
|
||||||
|
alloc = ['10.0.0.1', '10.0.0.2', '10.0.0.4', '10.0.0.5',
|
||||||
|
'10.0.0.6']
|
||||||
|
for i in range(len(alloc)):
|
||||||
|
self.assertEquals(ips[i]['ip_address'], alloc[i])
|
||||||
|
self.assertEquals(ips[i]['subnet_id'],
|
||||||
|
subnet['subnet']['id'])
|
||||||
|
with self.subnet(gateway='11.0.0.6',
|
||||||
|
cidr='11.0.0.0/29') as subnet:
|
||||||
|
kwargs = {"fixed_ips":
|
||||||
|
[{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id': subnet['subnet']['id']}]}
|
||||||
|
net_id = subnet['subnet']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port = self.deserialize(fmt, res)
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 5)
|
||||||
|
alloc = ['11.0.0.1', '11.0.0.2', '11.0.0.3', '11.0.0.4',
|
||||||
|
'11.0.0.5']
|
||||||
|
for i in range(len(alloc)):
|
||||||
|
self.assertEquals(ips[i]['ip_address'], alloc[i])
|
||||||
|
self.assertEquals(ips[i]['subnet_id'],
|
||||||
|
subnet['subnet']['id'])
|
||||||
|
|
||||||
|
def test_requested_invalid_fixed_ips(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
# Test invalid subnet_id
|
||||||
|
kwargs = {"fixed_ips":
|
||||||
|
[{'subnet_id': subnet['subnet']['id']},
|
||||||
|
{'subnet_id':
|
||||||
|
'00000000-ffff-ffff-ffff-000000000000'}]}
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port2 = self.deserialize(fmt, res)
|
||||||
|
self.assertEquals(res.status_int, 404)
|
||||||
|
|
||||||
|
# Test invalid IP address on specified subnet_id
|
||||||
|
kwargs = {"fixed_ips":
|
||||||
|
[{'subnet_id': subnet['subnet']['id'],
|
||||||
|
'ip_address': '1.1.1.1'}]}
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port2 = self.deserialize(fmt, res)
|
||||||
|
self.assertEquals(res.status_int, 400)
|
||||||
|
|
||||||
|
# Test invalid addresses - IP's not on subnet or network
|
||||||
|
# address or broadcast address
|
||||||
|
bad_ips = ['1.1.1.1', '10.0.0.0', '10.0.0.255']
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
for ip in bad_ips:
|
||||||
|
kwargs = {"fixed_ips": [{'ip_address': ip}]}
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port2 = self.deserialize(fmt, res)
|
||||||
|
self.assertEquals(res.status_int, 400)
|
||||||
|
|
||||||
|
# Enable allocation of gateway address
|
||||||
|
kwargs = {"fixed_ips":
|
||||||
|
[{'subnet_id': subnet['subnet']['id'],
|
||||||
|
'ip_address': '10.0.0.1'}]}
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port2 = self.deserialize(fmt, res)
|
||||||
|
ips = port2['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.1')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
self._delete('ports', port2['port']['id'])
|
||||||
|
|
||||||
|
def test_requested_split(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
# Allocate specific IP
|
||||||
|
kwargs = {"fixed_ips": [{'subnet_id': subnet['subnet']['id'],
|
||||||
|
'ip_address': '10.0.0.5'}]}
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port2 = self.deserialize(fmt, res)
|
||||||
|
ips = port2['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.5')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
# Allocate specific IP's
|
||||||
|
allocated = ['10.0.0.3', '10.0.0.4', '10.0.0.6']
|
||||||
|
for a in allocated:
|
||||||
|
res = self._create_port(fmt, net_id=net_id)
|
||||||
|
port2 = self.deserialize(fmt, res)
|
||||||
|
ips = port2['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], a)
|
||||||
|
self.assertEquals(ips[0]['subnet_id'],
|
||||||
|
subnet['subnet']['id'])
|
||||||
|
|
||||||
|
def test_requested_ips_only(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet() as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.0.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
ips_only = ['10.0.0.18', '10.0.0.20', '10.0.0.22', '10.0.0.21',
|
||||||
|
'10.0.0.3', '10.0.0.17', '10.0.0.19']
|
||||||
|
for i in ips_only:
|
||||||
|
kwargs = {"fixed_ips": [{'ip_address': i}]}
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||||
|
port = self.deserialize(fmt, res)
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], i)
|
||||||
|
self.assertEquals(ips[0]['subnet_id'],
|
||||||
|
subnet['subnet']['id'])
|
||||||
|
|
||||||
|
def test_recycling(self):
|
||||||
|
fmt = 'json'
|
||||||
|
with self.subnet(cidr='10.0.1.0/24') as subnet:
|
||||||
|
with self.port(subnet=subnet) as port:
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.1.2')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
net_id = port['port']['network_id']
|
||||||
|
ports = []
|
||||||
|
for i in range(16 - 3):
|
||||||
|
res = self._create_port(fmt, net_id=net_id)
|
||||||
|
p = self.deserialize(fmt, res)
|
||||||
|
ports.append(p)
|
||||||
|
for i in range(16 - 3):
|
||||||
|
x = random.randrange(0, len(ports), 1)
|
||||||
|
p = ports.pop(x)
|
||||||
|
self._delete('ports', p['port']['id'])
|
||||||
|
res = self._create_port(fmt, net_id=net_id)
|
||||||
|
port = self.deserialize(fmt, res)
|
||||||
|
ips = port['port']['fixed_ips']
|
||||||
|
self.assertEquals(len(ips), 1)
|
||||||
|
self.assertEquals(ips[0]['ip_address'], '10.0.1.3')
|
||||||
|
self.assertEquals(ips[0]['subnet_id'], subnet['subnet']['id'])
|
||||||
|
|
||||||
|
|
||||||
class TestNetworksV2(QuantumDbPluginV2TestCase):
|
class TestNetworksV2(QuantumDbPluginV2TestCase):
|
||||||
# NOTE(cerberus): successful network update and delete are
|
# NOTE(cerberus): successful network update and delete are
|
||||||
|
Loading…
x
Reference in New Issue
Block a user