neutron/neutron/db/models_v2.py
Rodolfo Alonso Hernandez 4b6c2246c0 Force "network_id" in "subnet" DB registers
The "subnet" OVO does not allow to have an empty (None) "network_id" but
the DB "subnet" table allows to have this parameter empty (NULL) in the
database. In order to avoid any problem like the one described in the
bug, this patch ensures the database "subnet" register does always have
a "network_id" value and if a "network" register is being deleted, all
related "subnet" registers are checked first.

Change-Id: Iad210f0585b4201fdb87187b44a9b42267b58db4
Closes-Bug: #1839658
2019-08-19 16:32:42 +00:00

280 lines
12 KiB
Python

# Copyright (c) 2012 OpenStack Foundation.
# All Rights Reserved.
#
# 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.
from neutron_lib.api.definitions import network as net_def
from neutron_lib.api.definitions import port as port_def
from neutron_lib.api.definitions import subnet as subnet_def
from neutron_lib.api.definitions import subnetpool as subnetpool_def
from neutron_lib import constants
from neutron_lib.db import constants as db_const
from neutron_lib.db import model_base
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy import sql
from neutron.db.network_dhcp_agent_binding import models as ndab_model
from neutron.db import rbac_db_models
from neutron.db import standard_attr
class IPAllocationPool(model_base.BASEV2, model_base.HasId):
"""Representation of an allocation pool in a Neutron subnet."""
subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id',
ondelete="CASCADE"),
nullable=True)
first_ip = sa.Column(sa.String(64), nullable=False)
last_ip = sa.Column(sa.String(64), nullable=False)
def __repr__(self):
return "%s - %s" % (self.first_ip, self.last_ip)
class IPAllocation(model_base.BASEV2):
"""Internal representation of allocated IP addresses in a Neutron subnet.
"""
port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id',
ondelete="CASCADE"),
nullable=True)
ip_address = sa.Column(sa.String(64), nullable=False, primary_key=True)
subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id',
ondelete="CASCADE"),
nullable=False, primary_key=True)
network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id",
ondelete="CASCADE"),
nullable=False, primary_key=True)
revises_on_change = ('port', )
class Route(object):
"""mixin of a route."""
destination = sa.Column(sa.String(64), nullable=False, primary_key=True)
nexthop = sa.Column(sa.String(64), nullable=False, primary_key=True)
class SubnetRoute(model_base.BASEV2, Route):
subnet_id = sa.Column(sa.String(36),
sa.ForeignKey('subnets.id',
ondelete="CASCADE"),
primary_key=True)
class Port(standard_attr.HasStandardAttributes, model_base.BASEV2,
model_base.HasId, model_base.HasProject):
"""Represents a port on a Neutron v2 network."""
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id"),
nullable=False)
fixed_ips = orm.relationship(IPAllocation,
backref=orm.backref('port',
load_on_pending=True),
lazy='subquery',
cascade='all, delete-orphan',
order_by=(IPAllocation.ip_address,
IPAllocation.subnet_id))
mac_address = sa.Column(sa.String(32), nullable=False)
admin_state_up = sa.Column(sa.Boolean(), nullable=False)
status = sa.Column(sa.String(16), nullable=False)
device_id = sa.Column(sa.String(db_const.DEVICE_ID_FIELD_SIZE),
nullable=False)
device_owner = sa.Column(sa.String(db_const.DEVICE_OWNER_FIELD_SIZE),
nullable=False)
ip_allocation = sa.Column(sa.String(16))
__table_args__ = (
sa.Index(
'ix_ports_network_id_mac_address', 'network_id', 'mac_address'),
sa.Index(
'ix_ports_network_id_device_owner', 'network_id', 'device_owner'),
sa.Index('ix_ports_device_id', 'device_id'),
sa.UniqueConstraint(
network_id, mac_address,
name='uniq_ports0network_id0mac_address'),
model_base.BASEV2.__table_args__
)
api_collections = [port_def.COLLECTION_NAME]
collection_resource_map = {port_def.COLLECTION_NAME:
port_def.RESOURCE_NAME}
tag_support = True
def __init__(self, id=None, tenant_id=None, project_id=None, name=None,
network_id=None, mac_address=None, admin_state_up=None,
status=None, device_id=None, device_owner=None,
fixed_ips=None, **kwargs):
super(Port, self).__init__(**kwargs)
self.id = id
self.project_id = project_id or tenant_id
self.name = name
self.network_id = network_id
self.mac_address = mac_address
self.admin_state_up = admin_state_up
self.device_owner = device_owner
self.device_id = device_id
# Since this is a relationship only set it if one is passed in.
if fixed_ips:
self.fixed_ips = fixed_ips
# NOTE(arosen): status must be set last as an event is triggered on!
self.status = status
class DNSNameServer(model_base.BASEV2):
"""Internal representation of a DNS nameserver."""
address = sa.Column(sa.String(128), nullable=False, primary_key=True)
subnet_id = sa.Column(sa.String(36),
sa.ForeignKey('subnets.id',
ondelete="CASCADE"),
primary_key=True)
order = sa.Column(sa.Integer, nullable=False, server_default='0')
class Subnet(standard_attr.HasStandardAttributes, model_base.BASEV2,
model_base.HasId, model_base.HasProject):
"""Represents a neutron subnet.
When a subnet is created the first and last entries will be created. These
are used for the IP allocation.
"""
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id'),
nullable=False)
# Added by the segments service plugin
segment_id = sa.Column(sa.String(36), sa.ForeignKey('networksegments.id'))
subnetpool_id = sa.Column(sa.String(36), index=True)
# NOTE: Explicitly specify join conditions for the relationship because
# subnetpool_id in subnet might be 'prefix_delegation' when the IPv6 Prefix
# Delegation is enabled
subnetpool = orm.relationship(
'SubnetPool', lazy='joined',
foreign_keys='Subnet.subnetpool_id',
primaryjoin='Subnet.subnetpool_id==SubnetPool.id')
ip_version = sa.Column(sa.Integer, nullable=False)
cidr = sa.Column(sa.String(64), nullable=False)
gateway_ip = sa.Column(sa.String(64))
network_standard_attr = orm.relationship(
'StandardAttribute', lazy='subquery', viewonly=True,
secondary='networks', uselist=False,
load_on_pending=True)
revises_on_change = ('network_standard_attr', )
allocation_pools = orm.relationship(IPAllocationPool,
backref='subnet',
lazy="subquery",
cascade='delete')
enable_dhcp = sa.Column(sa.Boolean())
dns_nameservers = orm.relationship(DNSNameServer,
backref='subnet',
cascade='all, delete, delete-orphan',
order_by=DNSNameServer.order,
lazy='subquery')
routes = orm.relationship(SubnetRoute,
backref='subnet',
cascade='all, delete, delete-orphan',
lazy='subquery')
ipv6_ra_mode = sa.Column(sa.Enum(constants.IPV6_SLAAC,
constants.DHCPV6_STATEFUL,
constants.DHCPV6_STATELESS,
name='ipv6_ra_modes'),
nullable=True)
ipv6_address_mode = sa.Column(sa.Enum(constants.IPV6_SLAAC,
constants.DHCPV6_STATEFUL,
constants.DHCPV6_STATELESS,
name='ipv6_address_modes'),
nullable=True)
# subnets don't have their own rbac_entries, they just inherit from
# the network rbac entries
rbac_entries = orm.relationship(
rbac_db_models.NetworkRBAC, lazy='subquery', uselist=True,
foreign_keys='Subnet.network_id',
primaryjoin='Subnet.network_id==NetworkRBAC.object_id')
api_collections = [subnet_def.COLLECTION_NAME]
collection_resource_map = {subnet_def.COLLECTION_NAME:
subnet_def.RESOURCE_NAME}
tag_support = True
class SubnetPoolPrefix(model_base.BASEV2):
"""Represents a neutron subnet pool prefix
"""
__tablename__ = 'subnetpoolprefixes'
cidr = sa.Column(sa.String(64), nullable=False, primary_key=True)
subnetpool_id = sa.Column(sa.String(36),
sa.ForeignKey('subnetpools.id',
ondelete='CASCADE'),
nullable=False,
primary_key=True)
class SubnetPool(standard_attr.HasStandardAttributes, model_base.BASEV2,
model_base.HasId, model_base.HasProject):
"""Represents a neutron subnet pool.
"""
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
ip_version = sa.Column(sa.Integer, nullable=False)
default_prefixlen = sa.Column(sa.Integer, nullable=False)
min_prefixlen = sa.Column(sa.Integer, nullable=False)
max_prefixlen = sa.Column(sa.Integer, nullable=False)
shared = sa.Column(sa.Boolean, nullable=False)
is_default = sa.Column(sa.Boolean, nullable=False,
server_default=sql.false())
default_quota = sa.Column(sa.Integer, nullable=True)
hash = sa.Column(sa.String(36), nullable=False, server_default='')
address_scope_id = sa.Column(sa.String(36), nullable=True)
prefixes = orm.relationship(SubnetPoolPrefix,
backref='subnetpools',
cascade='all, delete, delete-orphan',
lazy='subquery')
api_collections = [subnetpool_def.COLLECTION_NAME]
collection_resource_map = {subnetpool_def.COLLECTION_NAME:
subnetpool_def.RESOURCE_NAME}
tag_support = True
class Network(standard_attr.HasStandardAttributes, model_base.BASEV2,
model_base.HasId, model_base.HasProject):
"""Represents a v2 neutron network."""
name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
subnets = orm.relationship(
Subnet,
lazy="subquery")
status = sa.Column(sa.String(16))
admin_state_up = sa.Column(sa.Boolean)
vlan_transparent = sa.Column(sa.Boolean, nullable=True)
rbac_entries = orm.relationship(rbac_db_models.NetworkRBAC,
backref=orm.backref('network',
load_on_pending=True),
lazy='subquery',
cascade='all, delete, delete-orphan')
availability_zone_hints = sa.Column(sa.String(255))
# TODO(ihrachys) provide data migration path to fill in mtus for existing
# networks in Queens when all controllers run Pike+ code
mtu = sa.Column(sa.Integer, nullable=True)
dhcp_agents = orm.relationship(
'Agent', lazy='subquery', viewonly=True,
secondary=ndab_model.NetworkDhcpAgentBinding.__table__)
api_collections = [net_def.COLLECTION_NAME]
collection_resource_map = {net_def.COLLECTION_NAME: net_def.RESOURCE_NAME}
tag_support = True