Shared IP API

Squashed work from John Perkins <john.perkins@rackspace.com> and Thomas Maddox <thomas.maddox@rackspace.com>
This commit is contained in:
Thomas Maddox
2014-07-24 13:08:05 -05:00
parent cbb94af012
commit dc71941f78
11 changed files with 705 additions and 129 deletions

View File

@@ -14,19 +14,24 @@
# under the License.
from neutron.common import exceptions
from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging
from oslo.config import cfg
import webob
from quark.db import api as db_api
from quark.db import FIXED_IP
from quark.db import SHARED_IP
from quark import exceptions as quark_exceptions
from quark import ipam
from quark import plugin_views as v
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
ipam_driver = (importutils.import_class(CONF.QUARK.ipam_driver))()
def _get_ipam_driver_for_network(context, net_id):
return ipam.IPAM_REGISTRY.get_strategy(db_api.network_find(
context, id=net_id, scope=db_api.ONE)['ipam_strategy'])
def get_ip_addresses(context, **filters):
@@ -45,22 +50,54 @@ def get_ip_address(context, id):
return v._make_ip_dict(addr)
def create_ip_address(context, ip_address):
LOG.info("create_ip_address for tenant %s" % context.tenant_id)
def validate_ports_on_network_and_same_segment(ports, network_id):
first_segment = None
for port in ports:
addresses = port.get("ip_addresses", [])
for address in addresses:
if address["network_id"] != network_id:
raise exceptions.BadRequest(resource="ip_addresses",
msg="Must have ports connected to"
" the requested network")
segment_id = address.subnet.get("segment_id")
first_segment = first_segment or segment_id
if segment_id != first_segment:
raise exceptions.BadRequest(resource="ip_addresses",
msg="Segment id's do not match.")
port = None
ip_dict = ip_address["ip_address"]
port_ids = ip_dict.get('port_ids')
def _shared_ip_request(ip_address):
port_ids = ip_address.get('ip_address', {}).get('port_ids', [])
return len(port_ids) > 1
def _can_be_shared(address_model):
# Don't share IP if any of the assocs is enabled
return not any(a.enabled for a in address_model.associations)
def create_ip_address(context, body):
LOG.info("create_ip_address for tenant %s" % context.tenant_id)
address_type = SHARED_IP if _shared_ip_request(body) else FIXED_IP
ip_dict = body.get("ip_address")
port_ids = ip_dict.get('port_ids', [])
network_id = ip_dict.get('network_id')
device_ids = ip_dict.get('device_ids')
ip_version = ip_dict.get('version')
ip_address = ip_dict.get('ip_address')
# If no version is passed, you would get what the network provides,
# which could be both v4 and v6 addresses. Rather than allow for such
# an ambiguous outcome, we'll raise instead
if not ip_version:
raise exceptions.BadRequest(resource="ip_addresses",
msg="version is required.")
if not network_id:
raise exceptions.BadRequest(resource="ip_addresses",
msg="network_id is required.")
ipam_driver = _get_ipam_driver_for_network(context, network_id)
new_addresses = []
ports = []
if device_ids and not network_id:
raise exceptions.BadRequest(
resource="ip_addresses",
msg="network_id is required if device_ids are supplied.")
with context.session.begin():
if network_id and device_ids:
for device_id in device_ids:
@@ -70,6 +107,7 @@ def create_ip_address(context, ip_address):
ports.append(port)
elif port_ids:
for port_id in port_ids:
port = db_api.port_find(context, id=port_id,
tenant_id=context.tenant_id,
scope=db_api.ONE)
@@ -79,18 +117,25 @@ def create_ip_address(context, ip_address):
raise exceptions.PortNotFound(port_id=port_ids,
net_id=network_id)
address = ipam_driver.allocate_ip_address(
context,
port['network_id'],
port['id'],
CONF.QUARK.ipam_reuse_after,
ip_version,
ip_addresses=[ip_address])
validate_ports_on_network_and_same_segment(ports, network_id)
for port in ports:
port["ip_addresses"].append(address)
return v._make_ip_dict(address)
# Shared Ips are only new IPs. Two use cases: if we got device_id
# or if we got port_ids. We should check the case where we got port_ids
# and device_ids. The device_id must have a port on the network,
# and any port_ids must also be on that network already. If we have
# more than one port by this step, it's considered a shared IP,
# and therefore will be marked as unconfigured (enabled=False)
# for all ports.
ipam_driver.allocate_ip_address(context, new_addresses, network_id,
None, CONF.QUARK.ipam_reuse_after,
version=ip_version,
ip_addresses=[ip_address]
if ip_address else [],
address_type=address_type)
with context.session.begin():
new_address = db_api.port_associate_ip(context, ports,
new_addresses[0])
return v._make_ip_dict(new_address)
def _get_deallocated_override():
@@ -98,20 +143,28 @@ def _get_deallocated_override():
return '2000-01-01 00:00:00'
def _raise_if_shared_and_enabled(address_request, address_model):
if (_shared_ip_request(address_request)
and not _can_be_shared(address_model)):
raise exceptions.BadRequest(
resource="ip_addresses",
msg="This IP address is in use on another port and cannot be "
"shared")
def update_ip_address(context, id, ip_address):
LOG.info("update_ip_address %s for tenant %s" %
(id, context.tenant_id))
ports = []
with context.session.begin():
address = db_api.ip_address_find(
context, id=id, tenant_id=context.tenant_id, scope=db_api.ONE)
if not address:
raise exceptions.NotFound(
message="No IP address found with id=%s" % id)
reset = ip_address['ip_address'].get('reset_allocation_time',
False)
ipam_driver = _get_ipam_driver_for_network(context, address.network_id)
reset = ip_address['ip_address'].get('reset_allocation_time', False)
if reset and address['deallocated'] == 1:
if context.is_admin:
LOG.info("IP's deallocated time being manually reset")
@@ -120,27 +173,30 @@ def update_ip_address(context, id, ip_address):
msg = "Modification of reset_allocation_time requires admin"
raise webob.exc.HTTPForbidden(detail=msg)
old_ports = address['ports']
port_ids = ip_address['ip_address'].get('port_ids')
if port_ids is None:
return v._make_ip_dict(address)
for port in old_ports:
port['ip_addresses'].remove(address)
if port_ids:
ports = db_api.port_find(
context, tenant_id=context.tenant_id, id=port_ids,
scope=db_api.ALL)
# NOTE: could be considered inefficient because we're converting
# to a list to check length. Maybe revisit
_raise_if_shared_and_enabled(ip_address, address)
ports = db_api.port_find(context, tenant_id=context.tenant_id,
id=port_ids, scope=db_api.ALL)
# NOTE(name): could be considered inefficient because we're
# converting to a list to check length. Maybe revisit
if len(ports) != len(port_ids):
raise exceptions.NotFound(
message="No ports not found with ids=%s" % port_ids)
for port in ports:
port['ip_addresses'].extend([address])
else:
address["deallocated"] = 1
return v._make_ip_dict(address)
validate_ports_on_network_and_same_segment(ports,
address["network_id"])
LOG.info("Updating IP address, %s, to only be used by the"
"following ports: %s" % (address.address_readable,
[p.id for p in ports]))
new_address = db_api.update_port_associations_for_ip(context,
ports,
address)
else:
if port_ids is not None:
ipam_driver.deallocate_ip_address(
context, address)
return v._make_ip_dict(address)
return v._make_ip_dict(new_address)