193 lines
8.1 KiB
Python
193 lines
8.1 KiB
Python
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
# 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 oslo_config import cfg
|
|
from oslo_db import exception as db_exc
|
|
from oslo_log import helpers as log_helpers
|
|
from oslo_log import log as logging
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.orm import exc
|
|
|
|
from neutron.common import exceptions as n_exc
|
|
from neutron.common import utils
|
|
from neutron.db import model_base
|
|
from neutron.extensions import dvr as ext_dvr
|
|
from neutron.extensions import portbindings
|
|
from neutron.i18n import _LE
|
|
from neutron import manager
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
dvr_mac_address_opts = [
|
|
cfg.StrOpt('dvr_base_mac',
|
|
default="fa:16:3f:00:00:00",
|
|
help=_("The base mac address used for unique "
|
|
"DVR instances by Neutron. The first 3 octets will "
|
|
"remain unchanged. If the 4th octet is not 00, it will "
|
|
"also be used. The others will be randomly generated. "
|
|
"The 'dvr_base_mac' *must* be different from "
|
|
"'base_mac' to avoid mixing them up with MAC's "
|
|
"allocated for tenant ports. A 4 octet example would be "
|
|
"dvr_base_mac = fa:16:3f:4f:00:00. The default is 3 "
|
|
"octet")),
|
|
]
|
|
cfg.CONF.register_opts(dvr_mac_address_opts)
|
|
|
|
|
|
class DistributedVirtualRouterMacAddress(model_base.BASEV2):
|
|
"""Represents a v2 neutron distributed virtual router mac address."""
|
|
|
|
__tablename__ = 'dvr_host_macs'
|
|
|
|
host = sa.Column(sa.String(255), primary_key=True, nullable=False)
|
|
mac_address = sa.Column(sa.String(32), nullable=False, unique=True)
|
|
|
|
|
|
class DVRDbMixin(ext_dvr.DVRMacAddressPluginBase):
|
|
"""Mixin class to add dvr mac address to db_plugin_base_v2."""
|
|
|
|
@property
|
|
def plugin(self):
|
|
try:
|
|
if self._plugin is not None:
|
|
return self._plugin
|
|
except AttributeError:
|
|
pass
|
|
self._plugin = manager.NeutronManager.get_plugin()
|
|
return self._plugin
|
|
|
|
def _get_dvr_mac_address_by_host(self, context, host):
|
|
try:
|
|
query = context.session.query(DistributedVirtualRouterMacAddress)
|
|
dvrma = query.filter(
|
|
DistributedVirtualRouterMacAddress.host == host).one()
|
|
except exc.NoResultFound:
|
|
raise ext_dvr.DVRMacAddressNotFound(host=host)
|
|
return dvrma
|
|
|
|
def _create_dvr_mac_address(self, context, host):
|
|
"""Create DVR mac address for a given host."""
|
|
base_mac = cfg.CONF.dvr_base_mac.split(':')
|
|
max_retries = cfg.CONF.mac_generation_retries
|
|
for attempt in reversed(range(max_retries)):
|
|
try:
|
|
with context.session.begin(subtransactions=True):
|
|
mac_address = utils.get_random_mac(base_mac)
|
|
dvr_mac_binding = DistributedVirtualRouterMacAddress(
|
|
host=host, mac_address=mac_address)
|
|
context.session.add(dvr_mac_binding)
|
|
LOG.debug("Generated DVR mac for host %(host)s "
|
|
"is %(mac_address)s",
|
|
{'host': host, 'mac_address': mac_address})
|
|
dvr_macs = self.get_dvr_mac_address_list(context)
|
|
# TODO(vivek): improve scalability of this fanout by
|
|
# sending a single mac address rather than the entire set
|
|
self.notifier.dvr_mac_address_update(context, dvr_macs)
|
|
return self._make_dvr_mac_address_dict(dvr_mac_binding)
|
|
except db_exc.DBDuplicateEntry:
|
|
LOG.debug("Generated DVR mac %(mac)s exists."
|
|
" Remaining attempts %(attempts_left)s.",
|
|
{'mac': mac_address, 'attempts_left': attempt})
|
|
LOG.error(_LE("MAC generation error after %s attempts"), max_retries)
|
|
raise ext_dvr.MacAddressGenerationFailure(host=host)
|
|
|
|
def delete_dvr_mac_address(self, context, host):
|
|
query = context.session.query(DistributedVirtualRouterMacAddress)
|
|
(query.
|
|
filter(DistributedVirtualRouterMacAddress.host == host).
|
|
delete(synchronize_session=False))
|
|
|
|
def get_dvr_mac_address_list(self, context):
|
|
with context.session.begin(subtransactions=True):
|
|
return (context.session.
|
|
query(DistributedVirtualRouterMacAddress).all())
|
|
|
|
def get_dvr_mac_address_by_host(self, context, host):
|
|
"""Determine the MAC for the DVR port associated to host."""
|
|
if not host:
|
|
return
|
|
|
|
try:
|
|
return self._get_dvr_mac_address_by_host(context, host)
|
|
except ext_dvr.DVRMacAddressNotFound:
|
|
return self._create_dvr_mac_address(context, host)
|
|
|
|
def _make_dvr_mac_address_dict(self, dvr_mac_entry, fields=None):
|
|
return {'host': dvr_mac_entry['host'],
|
|
'mac_address': dvr_mac_entry['mac_address']}
|
|
|
|
@log_helpers.log_method_call
|
|
def get_ports_on_host_by_subnet(self, context, host, subnet):
|
|
"""Returns ports of interest, on a given subnet in the input host
|
|
|
|
This method returns ports that need to be serviced by DVR.
|
|
:param context: rpc request context
|
|
:param host: host id to match and extract ports of interest
|
|
:param subnet: subnet id to match and extract ports of interest
|
|
:returns list -- Ports on the given subnet in the input host
|
|
"""
|
|
# FIXME(vivek, salv-orlando): improve this query by adding the
|
|
# capability of filtering by binding:host_id
|
|
ports_by_host = []
|
|
filter = {'fixed_ips': {'subnet_id': [subnet]}}
|
|
ports = self.plugin.get_ports(context, filters=filter)
|
|
LOG.debug("List of Ports on subnet %(subnet)s at host %(host)s "
|
|
"received as %(ports)s",
|
|
{'subnet': subnet, 'host': host, 'ports': ports})
|
|
for port in ports:
|
|
device_owner = port['device_owner']
|
|
if (utils.is_dvr_serviced(device_owner)):
|
|
if port[portbindings.HOST_ID] == host:
|
|
port_dict = self.plugin._make_port_dict(port,
|
|
process_extensions=False)
|
|
ports_by_host.append(port_dict)
|
|
LOG.debug("Returning list of dvr serviced ports on host %(host)s"
|
|
" for subnet %(subnet)s ports %(ports)s",
|
|
{'host': host, 'subnet': subnet,
|
|
'ports': ports_by_host})
|
|
return ports_by_host
|
|
|
|
@log_helpers.log_method_call
|
|
def get_subnet_for_dvr(self, context, subnet, fixed_ips=None):
|
|
if fixed_ips:
|
|
subnet_data = fixed_ips[0]['subnet_id']
|
|
else:
|
|
subnet_data = subnet
|
|
try:
|
|
subnet_info = self.plugin.get_subnet(
|
|
context, subnet_data)
|
|
except n_exc.SubnetNotFound:
|
|
return {}
|
|
else:
|
|
# retrieve the gateway port on this subnet
|
|
if fixed_ips:
|
|
filter = fixed_ips[0]
|
|
else:
|
|
filter = {'fixed_ips': {'subnet_id': [subnet],
|
|
'ip_address':
|
|
[subnet_info['gateway_ip']]}}
|
|
|
|
internal_gateway_ports = self.plugin.get_ports(
|
|
context, filters=filter)
|
|
if not internal_gateway_ports:
|
|
LOG.error(_LE("Could not retrieve gateway port "
|
|
"for subnet %s"), subnet_info)
|
|
return {}
|
|
internal_port = internal_gateway_ports[0]
|
|
subnet_info['gateway_mac'] = internal_port['mac_address']
|
|
return subnet_info
|