Mark unaddressed ports with none in ip_allocation field
This is the Neutron side of the unaddressed ports blueprint. In order to allow unaddressed ports, Nova wants the port to explicitly say it is okay that it doesn't have any IP addresses. In Neutron, an unaddressed port is one that was created by explicitly passing [] in fixed_ips to create the port. A new DB field is added to the port to distinguish the unaddressed port case from the deferred IP allocation case where routed networks is involved. Change-Id: Ia61af4c14e955697a7d3fcc0bf4826a6d9475c98 Implements: blueprint vm-without-l3-address APIImpact: port now has ip_allocation attribute, set on port create
This commit is contained in:
parent
eec6ebd425
commit
b6a90df2ac
@ -47,8 +47,10 @@ from neutron.db import models_v2
|
|||||||
from neutron.db import rbac_db_mixin as rbac_mixin
|
from neutron.db import rbac_db_mixin as rbac_mixin
|
||||||
from neutron.db import rbac_db_models as rbac_db
|
from neutron.db import rbac_db_models as rbac_db
|
||||||
from neutron.db import standardattrdescription_db as stattr_db
|
from neutron.db import standardattrdescription_db as stattr_db
|
||||||
|
from neutron.extensions import ip_allocation as ipa
|
||||||
from neutron.extensions import l3
|
from neutron.extensions import l3
|
||||||
from neutron import ipam
|
from neutron import ipam
|
||||||
|
from neutron.ipam import exceptions as ipam_exc
|
||||||
from neutron.ipam import subnet_alloc
|
from neutron.ipam import subnet_alloc
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
from neutron import neutron_plugin_base_v2
|
from neutron import neutron_plugin_base_v2
|
||||||
@ -1138,7 +1140,17 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
|
|||||||
db_port = self._create_db_port_obj(context, port_data)
|
db_port = self._create_db_port_obj(context, port_data)
|
||||||
p['mac_address'] = db_port['mac_address']
|
p['mac_address'] = db_port['mac_address']
|
||||||
|
|
||||||
self.ipam.allocate_ips_for_port_and_store(context, port, port_id)
|
try:
|
||||||
|
self.ipam.allocate_ips_for_port_and_store(
|
||||||
|
context, port, port_id)
|
||||||
|
db_port['ip_allocation'] = ipa.IP_ALLOCATION_IMMEDIATE
|
||||||
|
except ipam_exc.DeferIpam:
|
||||||
|
db_port['ip_allocation'] = ipa.IP_ALLOCATION_DEFERRED
|
||||||
|
fixed_ips = p['fixed_ips']
|
||||||
|
if validators.is_attr_set(fixed_ips) and not fixed_ips:
|
||||||
|
# [] was passed explicitly as fixed_ips. An unaddressed port.
|
||||||
|
db_port['ip_allocation'] = ipa.IP_ALLOCATION_NONE
|
||||||
|
|
||||||
return db_port
|
return db_port
|
||||||
|
|
||||||
def _validate_port_for_update(self, context, db_port, new_port, new_mac):
|
def _validate_port_for_update(self, context, db_port, new_port, new_mac):
|
||||||
|
@ -660,7 +660,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
|
|||||||
if query.limit(1).count():
|
if query.limit(1).count():
|
||||||
# No, must be a deferred IP port because there are matching
|
# No, must be a deferred IP port because there are matching
|
||||||
# subnets. Happens on routed networks when host isn't known.
|
# subnets. Happens on routed networks when host isn't known.
|
||||||
return []
|
raise ipam_exceptions.DeferIpam()
|
||||||
|
|
||||||
raise ipam_exceptions.IpAddressGenerationFailureNoMatchingSubnet()
|
raise ipam_exceptions.IpAddressGenerationFailureNoMatchingSubnet()
|
||||||
|
|
||||||
|
@ -280,8 +280,12 @@ class IpamPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||||||
removed = []
|
removed = []
|
||||||
changes = self._get_changed_ips_for_port(
|
changes = self._get_changed_ips_for_port(
|
||||||
context, original_ips, new_ips, port['device_owner'])
|
context, original_ips, new_ips, port['device_owner'])
|
||||||
subnets = self._ipam_get_subnets(
|
try:
|
||||||
context, network_id=port['network_id'], host=host)
|
subnets = self._ipam_get_subnets(
|
||||||
|
context, network_id=port['network_id'], host=host)
|
||||||
|
except ipam_exc.DeferIpam:
|
||||||
|
subnets = []
|
||||||
|
|
||||||
# Check if the IP's to add are OK
|
# Check if the IP's to add are OK
|
||||||
to_add = self._test_fixed_ips_for_port(
|
to_add = self._test_fixed_ips_for_port(
|
||||||
context, port['network_id'], changes.add,
|
context, port['network_id'], changes.add,
|
||||||
|
@ -1 +1 @@
|
|||||||
6b461a21bcfc
|
5cd92597d11d
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Add ip_allocation to port """
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '5cd92597d11d'
|
||||||
|
down_revision = '6b461a21bcfc'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('ports',
|
||||||
|
sa.Column('ip_allocation',
|
||||||
|
sa.String(length=16),
|
||||||
|
nullable=True))
|
@ -133,6 +133,8 @@ class Port(standard_attr.HasStandardAttributes, model_base.BASEV2,
|
|||||||
device_id = sa.Column(sa.String(attr.DEVICE_ID_MAX_LEN), nullable=False)
|
device_id = sa.Column(sa.String(attr.DEVICE_ID_MAX_LEN), nullable=False)
|
||||||
device_owner = sa.Column(sa.String(attr.DEVICE_OWNER_MAX_LEN),
|
device_owner = sa.Column(sa.String(attr.DEVICE_OWNER_MAX_LEN),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
|
ip_allocation = sa.Column(sa.String(16))
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
sa.Index(
|
sa.Index(
|
||||||
'ix_ports_network_id_mac_address', 'network_id', 'mac_address'),
|
'ix_ports_network_id_mac_address', 'network_id', 'mac_address'),
|
||||||
|
@ -84,3 +84,7 @@ class IpamValueInvalid(exceptions.Conflict):
|
|||||||
def __init__(self, message=None):
|
def __init__(self, message=None):
|
||||||
self.message = message
|
self.message = message
|
||||||
super(IpamValueInvalid, self).__init__()
|
super(IpamValueInvalid, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
class DeferIpam(exceptions.NeutronException):
|
||||||
|
message = _("Exception used to signal that IP allocation is deferred")
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from sqlalchemy.orm import session
|
|
||||||
|
|
||||||
from neutron._i18n import _
|
from neutron._i18n import _
|
||||||
from neutron.api.v2 import attributes
|
from neutron.api.v2 import attributes
|
||||||
from neutron.callbacks import events
|
from neutron.callbacks import events
|
||||||
@ -50,15 +48,8 @@ def _extend_port_dict_binding(plugin, port_res, port_db):
|
|||||||
return
|
return
|
||||||
|
|
||||||
value = ip_allocation.IP_ALLOCATION_IMMEDIATE
|
value = ip_allocation.IP_ALLOCATION_IMMEDIATE
|
||||||
if not port_res.get('fixed_ips'):
|
if port_db.get('ip_allocation'):
|
||||||
# NOTE Only routed network ports have deferred allocation. Check if it
|
value = port_db.get('ip_allocation')
|
||||||
# is routed by looking for subnets associated with segments.
|
|
||||||
object_session = session.Session.object_session(port_db)
|
|
||||||
query = object_session.query(models_v2.Subnet)
|
|
||||||
query = query.filter_by(network_id=port_db.network_id)
|
|
||||||
query = query.filter(models_v2.Subnet.segment_id.isnot(None))
|
|
||||||
if query.count():
|
|
||||||
value = ip_allocation.IP_ALLOCATION_DEFERRED
|
|
||||||
port_res[ip_allocation.IP_ALLOCATION] = value
|
port_res[ip_allocation.IP_ALLOCATION] = value
|
||||||
|
|
||||||
|
|
||||||
|
@ -986,6 +986,46 @@ class TestSegmentAwareIpam(SegmentTestCase):
|
|||||||
# Gets bad request because there are no eligible subnets.
|
# Gets bad request because there are no eligible subnets.
|
||||||
self.assertEqual(webob.exc.HTTPBadRequest.code, response.status_int)
|
self.assertEqual(webob.exc.HTTPBadRequest.code, response.status_int)
|
||||||
|
|
||||||
|
def _create_port_and_show(self, network, **kwargs):
|
||||||
|
response = self._create_port(
|
||||||
|
self.fmt,
|
||||||
|
net_id=network['network']['id'],
|
||||||
|
tenant_id=network['network']['tenant_id'],
|
||||||
|
**kwargs)
|
||||||
|
port = self.deserialize(self.fmt, response)
|
||||||
|
request = self.new_show_request('ports', port['port']['id'])
|
||||||
|
return self.deserialize(self.fmt, request.get_response(self.api))
|
||||||
|
|
||||||
|
def test_port_create_with_no_fixed_ips_no_ipam_on_routed_network(self):
|
||||||
|
"""Ports requesting no fixed_ips not deferred, even on routed net"""
|
||||||
|
with self.network() as network:
|
||||||
|
segment = self._test_create_segment(
|
||||||
|
network_id=network['network']['id'],
|
||||||
|
physical_network='physnet',
|
||||||
|
network_type=p_constants.TYPE_VLAN)
|
||||||
|
with self.subnet(network=network,
|
||||||
|
segment_id=segment['segment']['id']):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Create an unbound port requesting no IP addresses
|
||||||
|
response = self._create_port_and_show(network, fixed_ips=[])
|
||||||
|
self.assertEqual([], response['port']['fixed_ips'])
|
||||||
|
self.assertEqual(ip_allocation.IP_ALLOCATION_NONE,
|
||||||
|
response['port'][ip_allocation.IP_ALLOCATION])
|
||||||
|
|
||||||
|
def test_port_create_with_no_fixed_ips_no_ipam(self):
|
||||||
|
"""Ports without addresses on non-routed networks are not deferred"""
|
||||||
|
with self.network() as network:
|
||||||
|
with self.subnet(network=network):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Create an unbound port requesting no IP addresses
|
||||||
|
response = self._create_port_and_show(network, fixed_ips=[])
|
||||||
|
|
||||||
|
self.assertEqual([], response['port']['fixed_ips'])
|
||||||
|
self.assertEqual(ip_allocation.IP_ALLOCATION_NONE,
|
||||||
|
response['port'][ip_allocation.IP_ALLOCATION])
|
||||||
|
|
||||||
def test_port_without_ip_not_deferred(self):
|
def test_port_without_ip_not_deferred(self):
|
||||||
"""Ports without addresses on non-routed networks are not deferred"""
|
"""Ports without addresses on non-routed networks are not deferred"""
|
||||||
with self.network() as network:
|
with self.network() as network:
|
||||||
@ -1001,6 +1041,18 @@ class TestSegmentAwareIpam(SegmentTestCase):
|
|||||||
request = self.new_show_request('ports', port['port']['id'])
|
request = self.new_show_request('ports', port['port']['id'])
|
||||||
response = self.deserialize(self.fmt, request.get_response(self.api))
|
response = self.deserialize(self.fmt, request.get_response(self.api))
|
||||||
|
|
||||||
|
self.assertEqual([], response['port']['fixed_ips'])
|
||||||
|
self.assertEqual(ip_allocation.IP_ALLOCATION_IMMEDIATE,
|
||||||
|
response['port'][ip_allocation.IP_ALLOCATION])
|
||||||
|
|
||||||
|
def test_port_without_ip_not_deferred_no_binding(self):
|
||||||
|
"""Ports without addresses on non-routed networks are not deferred"""
|
||||||
|
with self.network() as network:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Create a unbound port with no IP address (since there is no subnet)
|
||||||
|
response = self._create_port_and_show(network)
|
||||||
|
self.assertEqual([], response['port']['fixed_ips'])
|
||||||
self.assertEqual(ip_allocation.IP_ALLOCATION_IMMEDIATE,
|
self.assertEqual(ip_allocation.IP_ALLOCATION_IMMEDIATE,
|
||||||
response['port'][ip_allocation.IP_ALLOCATION])
|
response['port'][ip_allocation.IP_ALLOCATION])
|
||||||
|
|
||||||
@ -1026,7 +1078,6 @@ class TestSegmentAwareIpam(SegmentTestCase):
|
|||||||
# Create the subnet and try to update the port to get an IP
|
# Create the subnet and try to update the port to get an IP
|
||||||
with self.subnet(network=network,
|
with self.subnet(network=network,
|
||||||
segment_id=segment['segment']['id']) as subnet:
|
segment_id=segment['segment']['id']) as subnet:
|
||||||
self._validate_deferred_ip_allocation(port['port']['id'])
|
|
||||||
self._validate_l2_adjacency(network['network']['id'],
|
self._validate_l2_adjacency(network['network']['id'],
|
||||||
is_adjacent=False)
|
is_adjacent=False)
|
||||||
# Try requesting an IP (but the only subnet is on a segment)
|
# Try requesting an IP (but the only subnet is on a segment)
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
Add ip_allocation attribute to port resources
|
||||||
|
features:
|
||||||
|
- The port resource now has an ip_allocation attribute.
|
||||||
|
The value of this attribute will be set to
|
||||||
|
'immediate', 'deferred', or 'none' at the time the
|
||||||
|
port is created. It will not be changed when the port
|
||||||
|
is updated.
|
||||||
|
|
||||||
|
'immediate' means that the port is expected to have
|
||||||
|
an IP address and Neutron attempted IP allocation on
|
||||||
|
port creation. 'deferred' means that the port is
|
||||||
|
expected to have an IP address but Neutron deferred
|
||||||
|
IP allocation until a port update provides the host
|
||||||
|
to which the port will be bound. 'none' means that
|
||||||
|
the port was created explicitly with no addresses by
|
||||||
|
passing [] in fixed_ips when creating it.
|
||||||
|
upgrade:
|
||||||
|
- All existing ports are considered to have 'immediate'
|
||||||
|
IP allocation. Any ports that do not have this
|
||||||
|
attribute should also be considered to have immediate
|
||||||
|
IP allocation.
|
Loading…
Reference in New Issue
Block a user