neutron/neutron/plugins/ml2/driver_context.py

331 lines
12 KiB
Python

# Copyright (c) 2013 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 portbindings
from neutron_lib import constants
from neutron_lib.plugins.ml2 import api
from oslo_log import log
from oslo_serialization import jsonutils
import sqlalchemy
from neutron.db import segments_db
LOG = log.getLogger(__name__)
class InstanceSnapshot(object):
"""Used to avoid holding references to DB objects in PortContext."""
def __init__(self, obj):
self._model_class = obj.__class__
self._identity_key = sqlalchemy.orm.util.identity_key(instance=obj)[1]
self._cols = [col.key
for col in sqlalchemy.inspect(self._model_class).columns]
for col in self._cols:
setattr(self, col, getattr(obj, col))
def persist_state_to_session(self, session):
"""Updates the state of the snapshot in the session.
Finds the SQLA object in the session if it exists or creates a new
object and updates the object with the column values stored in this
snapshot.
"""
db_obj = session.query(self._model_class).get(self._identity_key)
if db_obj:
for col in self._cols:
setattr(db_obj, col, getattr(self, col))
else:
session.add(self._model_class(**{col: getattr(self, col)
for col in self._cols}))
def __getitem__(self, item):
if item not in self._cols:
raise KeyError(item)
return getattr(self, item)
class MechanismDriverContext(object):
"""MechanismDriver context base class."""
def __init__(self, plugin, plugin_context):
self._plugin = plugin
# This temporarily creates a reference loop, but the
# lifetime of PortContext is limited to a single
# method call of the plugin.
self._plugin_context = plugin_context
class NetworkContext(MechanismDriverContext, api.NetworkContext):
def __init__(self, plugin, plugin_context, network,
original_network=None, segments=None):
super(NetworkContext, self).__init__(plugin, plugin_context)
self._network = network
self._original_network = original_network
self._segments = segments_db.get_network_segments(
plugin_context, network['id']) if segments is None else segments
@property
def current(self):
return self._network
@property
def original(self):
return self._original_network
@property
def network_segments(self):
return self._segments
class SubnetContext(MechanismDriverContext, api.SubnetContext):
def __init__(self, plugin, plugin_context, subnet, network,
original_subnet=None):
super(SubnetContext, self).__init__(plugin, plugin_context)
self._subnet = subnet
self._original_subnet = original_subnet
self._network_context = NetworkContext(plugin, plugin_context,
network) if network else None
@property
def current(self):
return self._subnet
@property
def original(self):
return self._original_subnet
@property
def network(self):
if self._network_context is None:
network = self._plugin.get_network(
self._plugin_context, self.current['network_id'])
self._network_context = NetworkContext(
self._plugin, self._plugin_context, network)
return self._network_context
class PortContext(MechanismDriverContext, api.PortContext):
def __init__(self, plugin, plugin_context, port, network, binding,
binding_levels, original_port=None):
super(PortContext, self).__init__(plugin, plugin_context)
self._port = port
self._original_port = original_port
if isinstance(network, NetworkContext):
self._network_context = network
else:
self._network_context = NetworkContext(
plugin, plugin_context, network) if network else None
# NOTE(kevinbenton): InstanceSnapshot can go away once we are working
# with OVO objects instead of native SQLA objects.
self._binding = InstanceSnapshot(binding)
self._binding_levels = binding_levels or []
self._segments_to_bind = None
self._new_bound_segment = None
self._next_segments_to_bind = None
if original_port:
self._original_vif_type = binding.vif_type
self._original_vif_details = self._plugin._get_vif_details(binding)
self._original_binding_levels = self._binding_levels
else:
self._original_vif_type = None
self._original_vif_details = None
self._original_binding_levels = None
self._new_port_status = None
# The following methods are for use by the ML2 plugin and are not
# part of the driver API.
def _prepare_to_bind(self, segments_to_bind):
self._segments_to_bind = segments_to_bind
self._new_bound_segment = None
self._next_segments_to_bind = None
def _clear_binding_levels(self):
self._binding_levels = []
def _push_binding_level(self, binding_level):
# NOTE(slaweq): binding_level should be always OVO with no reference
# to DB object
self._binding_levels.append(binding_level)
def _pop_binding_level(self):
return self._binding_levels.pop()
# The following implement the abstract methods and properties of
# the driver API.
@property
def current(self):
return self._port
@property
def original(self):
return self._original_port
@property
def status(self):
# REVISIT(rkukura): Eliminate special DVR case as part of
# resolving bug 1367391?
if self._port['device_owner'] == constants.DEVICE_OWNER_DVR_INTERFACE:
return self._binding.status
return self._port['status']
@property
def original_status(self):
# REVISIT(rkukura): Should return host-specific status for DVR
# ports. Fix as part of resolving bug 1367391.
if self._original_port:
return self._original_port['status']
@property
def network(self):
if not self._network_context:
network = self._plugin.get_network(
self._plugin_context, self.current['network_id'])
self._network_context = NetworkContext(
self._plugin, self._plugin_context, network)
return self._network_context
@property
def binding_levels(self):
if self._binding_levels:
return [{
api.BOUND_DRIVER: level.driver,
api.BOUND_SEGMENT: self._expand_segment(level.segment_id)
} for level in self._binding_levels]
@property
def original_binding_levels(self):
if self._original_binding_levels:
return [{
api.BOUND_DRIVER: level.driver,
api.BOUND_SEGMENT: self._expand_segment(level.segment_id)
} for level in self._original_binding_levels]
@property
def top_bound_segment(self):
if self._binding_levels:
return self._expand_segment(self._binding_levels[0].segment_id)
@property
def original_top_bound_segment(self):
if self._original_binding_levels:
return self._expand_segment(
self._original_binding_levels[0].segment_id)
@property
def bottom_bound_segment(self):
if self._binding_levels:
return self._expand_segment(self._binding_levels[-1].segment_id)
@property
def original_bottom_bound_segment(self):
if self._original_binding_levels:
return self._expand_segment(
self._original_binding_levels[-1].segment_id)
def _expand_segment(self, segment_id):
for s in self.network.network_segments:
if s['id'] == segment_id:
return s
# TODO(kevinbenton): eliminate the query below. The above should
# always return since the port is bound to a network segment. Leaving
# in for now for minimally invasive change for back-port.
segment = segments_db.get_segment_by_id(self._plugin_context,
segment_id)
if not segment:
LOG.warning("Could not expand segment %s", segment_id)
return segment
@property
def host(self):
# REVISIT(rkukura): Eliminate special DVR case as part of
# resolving bug 1367391?
if self._port['device_owner'] == constants.DEVICE_OWNER_DVR_INTERFACE:
return self._binding.host
return self._port.get(portbindings.HOST_ID)
@property
def original_host(self):
# REVISIT(rkukura): Eliminate special DVR case as part of
# resolving bug 1367391?
if self._port['device_owner'] == constants.DEVICE_OWNER_DVR_INTERFACE:
return self._original_port and self._binding.host
else:
return (self._original_port and
self._original_port.get(portbindings.HOST_ID))
@property
def vif_type(self):
return self._binding.vif_type
@property
def original_vif_type(self):
return self._original_vif_type
@property
def vif_details(self):
return self._plugin._get_vif_details(self._binding)
@property
def original_vif_details(self):
return self._original_vif_details
@property
def segments_to_bind(self):
return self._segments_to_bind
def host_agents(self, agent_type):
return self._plugin.get_agents(self._plugin_context,
filters={'agent_type': [agent_type],
'host': [self._binding.host]})
def set_binding(self, segment_id, vif_type, vif_details,
status=None):
# TODO(rkukura) Verify binding allowed, segment in network
self._new_bound_segment = segment_id
self._binding.vif_type = vif_type
self._binding.vif_details = jsonutils.dumps(vif_details)
self._new_port_status = status
def _unset_binding(self):
'''Undo a previous call to set_binding() before it gets committed.
This method is for MechanismManager and is not part of the driver API.
'''
self._new_bound_segment = None
self._binding.vif_type = portbindings.VIF_TYPE_UNBOUND
self._binding.vif_details = ''
self._new_port_status = None
def continue_binding(self, segment_id, next_segments_to_bind):
# TODO(rkukura) Verify binding allowed, segment in network
self._new_bound_segment = segment_id
self._next_segments_to_bind = next_segments_to_bind
def allocate_dynamic_segment(self, segment):
network_id = self._network_context.current['id']
return self._plugin.type_manager.allocate_dynamic_segment(
self._plugin_context, network_id, segment)
def release_dynamic_segment(self, segment_id):
return self._plugin.type_manager.release_dynamic_segment(
self._plugin_context, segment_id)