88dc6ea2eb
When deleting a non-existing port, the error message would be "Port XXXX could not be found on network None" for some plugins. "network None" is not correct enough here. Fixes bug #1203631 Change-Id: Ie5d1ec99a2726ff3fec07f83f83a12d24613ae57
216 lines
8.1 KiB
Python
216 lines
8.1 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
|
|
# 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 sqlalchemy import exc as sa_exc
|
|
from sqlalchemy import func
|
|
from sqlalchemy.orm import exc as orm_exc
|
|
|
|
from neutron.common import exceptions as q_exc
|
|
import neutron.db.api as db
|
|
from neutron.db import models_v2
|
|
from neutron.db import securitygroups_db as sg_db
|
|
from neutron.extensions import securitygroup as ext_sg
|
|
from neutron import manager
|
|
from neutron.openstack.common import log as logging
|
|
from neutron.plugins.ryu.db import models_v2 as ryu_models_v2
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def network_all_tenant_list():
|
|
session = db.get_session()
|
|
return session.query(models_v2.Network).all()
|
|
|
|
|
|
def get_port_from_device(port_id):
|
|
LOG.debug(_("get_port_from_device() called:port_id=%s"), port_id)
|
|
session = db.get_session()
|
|
sg_binding_port = sg_db.SecurityGroupPortBinding.port_id
|
|
|
|
query = session.query(models_v2.Port,
|
|
sg_db.SecurityGroupPortBinding.security_group_id)
|
|
query = query.outerjoin(sg_db.SecurityGroupPortBinding,
|
|
models_v2.Port.id == sg_binding_port)
|
|
query = query.filter(models_v2.Port.id == port_id)
|
|
port_and_sgs = query.all()
|
|
if not port_and_sgs:
|
|
return None
|
|
port = port_and_sgs[0][0]
|
|
plugin = manager.NeutronManager.get_plugin()
|
|
port_dict = plugin._make_port_dict(port)
|
|
port_dict[ext_sg.SECURITYGROUPS] = [
|
|
sg_id for port_, sg_id in port_and_sgs if sg_id]
|
|
port_dict['security_group_rules'] = []
|
|
port_dict['security_group_source_groups'] = []
|
|
port_dict['fixed_ips'] = [ip['ip_address'] for ip in port['fixed_ips']]
|
|
return port_dict
|
|
|
|
|
|
class TunnelKey(object):
|
|
# VLAN: 12 bits
|
|
# GRE, VXLAN: 24bits
|
|
# TODO(yamahata): STT: 64bits
|
|
_KEY_MIN_HARD = 1
|
|
_KEY_MAX_HARD = 0xffffffff
|
|
|
|
def __init__(self, key_min=_KEY_MIN_HARD, key_max=_KEY_MAX_HARD):
|
|
self.key_min = key_min
|
|
self.key_max = key_max
|
|
|
|
if (key_min < self._KEY_MIN_HARD or key_max > self._KEY_MAX_HARD or
|
|
key_min > key_max):
|
|
raise ValueError(_('Invalid tunnel key options '
|
|
'tunnel_key_min: %(key_min)d '
|
|
'tunnel_key_max: %(key_max)d. '
|
|
'Using default value') % {'key_min': key_min,
|
|
'key_max': key_max})
|
|
|
|
def _last_key(self, session):
|
|
try:
|
|
return session.query(ryu_models_v2.TunnelKeyLast).one()
|
|
except orm_exc.MultipleResultsFound:
|
|
max_key = session.query(
|
|
func.max(ryu_models_v2.TunnelKeyLast.last_key))
|
|
if max_key > self.key_max:
|
|
max_key = self.key_min
|
|
|
|
session.query(ryu_models_v2.TunnelKeyLast).delete()
|
|
last_key = ryu_models_v2.TunnelKeyLast(last_key=max_key)
|
|
except orm_exc.NoResultFound:
|
|
last_key = ryu_models_v2.TunnelKeyLast(last_key=self.key_min)
|
|
|
|
session.add(last_key)
|
|
session.flush()
|
|
return session.query(ryu_models_v2.TunnelKeyLast).one()
|
|
|
|
def _find_key(self, session, last_key):
|
|
"""Try to find unused tunnel key.
|
|
|
|
Trying to find unused tunnel key in TunnelKey table starting
|
|
from last_key + 1.
|
|
When all keys are used, raise sqlalchemy.orm.exc.NoResultFound
|
|
"""
|
|
# key 0 is used for special meanings. So don't allocate 0.
|
|
|
|
# sqlite doesn't support
|
|
# '(select order by limit) union all (select order by limit) '
|
|
# 'order by limit'
|
|
# So do it manually
|
|
# new_key = session.query("new_key").from_statement(
|
|
# # If last_key + 1 isn't used, it's the result
|
|
# 'SELECT new_key '
|
|
# 'FROM (SELECT :last_key + 1 AS new_key) q1 '
|
|
# 'WHERE NOT EXISTS '
|
|
# '(SELECT 1 FROM tunnelkeys WHERE tunnel_key = :last_key + 1) '
|
|
#
|
|
# 'UNION ALL '
|
|
#
|
|
# # if last_key + 1 used,
|
|
# # find the least unused key from last_key + 1
|
|
# '(SELECT t.tunnel_key + 1 AS new_key '
|
|
# 'FROM tunnelkeys t '
|
|
# 'WHERE NOT EXISTS '
|
|
# '(SELECT 1 FROM tunnelkeys ti '
|
|
# ' WHERE ti.tunnel_key = t.tunnel_key + 1) '
|
|
# 'AND t.tunnel_key >= :last_key '
|
|
# 'ORDER BY new_key LIMIT 1) '
|
|
#
|
|
# 'ORDER BY new_key LIMIT 1'
|
|
# ).params(last_key=last_key).one()
|
|
try:
|
|
new_key = session.query("new_key").from_statement(
|
|
# If last_key + 1 isn't used, it's the result
|
|
'SELECT new_key '
|
|
'FROM (SELECT :last_key + 1 AS new_key) q1 '
|
|
'WHERE NOT EXISTS '
|
|
'(SELECT 1 FROM tunnelkeys WHERE tunnel_key = :last_key + 1) '
|
|
).params(last_key=last_key).one()
|
|
except orm_exc.NoResultFound:
|
|
new_key = session.query("new_key").from_statement(
|
|
# if last_key + 1 used,
|
|
# find the least unused key from last_key + 1
|
|
'(SELECT t.tunnel_key + 1 AS new_key '
|
|
'FROM tunnelkeys t '
|
|
'WHERE NOT EXISTS '
|
|
'(SELECT 1 FROM tunnelkeys ti '
|
|
' WHERE ti.tunnel_key = t.tunnel_key + 1) '
|
|
'AND t.tunnel_key >= :last_key '
|
|
'ORDER BY new_key LIMIT 1) '
|
|
).params(last_key=last_key).one()
|
|
|
|
new_key = new_key[0] # the result is tuple.
|
|
LOG.debug(_("last_key %(last_key)s new_key %(new_key)s"),
|
|
{'last_key': last_key, 'new_key': new_key})
|
|
if new_key > self.key_max:
|
|
LOG.debug(_("No key found"))
|
|
raise orm_exc.NoResultFound()
|
|
return new_key
|
|
|
|
def _allocate(self, session, network_id):
|
|
last_key = self._last_key(session)
|
|
try:
|
|
new_key = self._find_key(session, last_key.last_key)
|
|
except orm_exc.NoResultFound:
|
|
new_key = self._find_key(session, self.key_min)
|
|
|
|
tunnel_key = ryu_models_v2.TunnelKey(network_id=network_id,
|
|
tunnel_key=new_key)
|
|
last_key.last_key = new_key
|
|
session.add(tunnel_key)
|
|
return new_key
|
|
|
|
_TRANSACTION_RETRY_MAX = 16
|
|
|
|
def allocate(self, session, network_id):
|
|
count = 0
|
|
while True:
|
|
session.begin(subtransactions=True)
|
|
try:
|
|
new_key = self._allocate(session, network_id)
|
|
session.commit()
|
|
break
|
|
except sa_exc.SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
count += 1
|
|
if count > self._TRANSACTION_RETRY_MAX:
|
|
# if this happens too often, increase _TRANSACTION_RETRY_MAX
|
|
LOG.warn(_("Transaction retry exhausted (%d). "
|
|
"Abandoned tunnel key allocation."), count)
|
|
raise q_exc.ResourceExhausted()
|
|
|
|
return new_key
|
|
|
|
def delete(self, session, network_id):
|
|
session.query(ryu_models_v2.TunnelKey).filter_by(
|
|
network_id=network_id).delete()
|
|
session.flush()
|
|
|
|
def all_list(self):
|
|
session = db.get_session()
|
|
return session.query(ryu_models_v2.TunnelKey).all()
|
|
|
|
|
|
def set_port_status(session, port_id, status):
|
|
try:
|
|
port = session.query(models_v2.Port).filter_by(id=port_id).one()
|
|
port['status'] = status
|
|
session.merge(port)
|
|
session.flush()
|
|
except orm_exc.NoResultFound:
|
|
raise q_exc.PortNotFound(port_id=port_id)
|