Shared IP API
Squashed work from John Perkins <john.perkins@rackspace.com> and Thomas Maddox <thomas.maddox@rackspace.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user