Brian Haley b79842f289 Start enforcing E125 flake8 directive
Removed E125 (continuation line does not distinguish itself
from next logical line) from the ignore list and fixed all
the indentation issues.  Didn't think it was going to be
close to 100 files when I started.

Change-Id: I0a6f5efec4b7d8d3632dd9dbb43e0ab58af9dff3
2019-07-19 23:39:41 -04:00

478 lines
18 KiB
Python

# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
#
# 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 import constants
from oslo_config import cfg
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from neutron.agent.common import utils as common_utils
from neutron.agent.l3 import dvr_snat_ns
from neutron.agent.l3 import namespaces
from neutron.agent.linux import ip_lib
from neutron.agent.linux import iptables_manager
from neutron.common import ipv6_utils
from neutron.conf.agent import common as config
from neutron.services.metering.drivers import abstract_driver
LOG = logging.getLogger(__name__)
NS_PREFIX = 'qrouter-'
WRAP_NAME = 'neutron-meter'
EXTERNAL_DEV_PREFIX = 'qg-'
ROUTER_2_FIP_DEV_PREFIX = namespaces.ROUTER_2_FIP_DEV_PREFIX
TOP_CHAIN = WRAP_NAME + "-FORWARD"
RULE = '-r-'
LABEL = '-l-'
config.register_interface_driver_opts_helper(cfg.CONF)
config.register_interface_opts()
class IptablesManagerTransaction(object):
__transactions = {}
def __init__(self, im):
self.im = im
transaction = self.__transactions.get(im, 0)
transaction += 1
self.__transactions[im] = transaction
def __enter__(self):
return self.im
def __exit__(self, type, value, traceback):
transaction = self.__transactions.get(self.im)
if transaction == 1:
self.im.apply()
del self.__transactions[self.im]
else:
transaction -= 1
self.__transactions[self.im] = transaction
class RouterWithMetering(object):
def __init__(self, conf, router):
self.conf = conf
self.id = router['id']
self.router = router
# TODO(cbrandily): deduplicate ns_name generation in metering/l3
self.ns_name = NS_PREFIX + self.id
self.iptables_manager = None
self.snat_iptables_manager = None
self.metering_labels = {}
self.create_iptables_managers()
def create_iptables_managers(self):
"""Creates iptables managers if the are not already created
Returns True if any manager is created
"""
created = False
if self.router['distributed'] and self.snat_iptables_manager is None:
# If distributed routers then we need to apply the
# metering agent label rules in the snat namespace as well.
snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(
self.id)
# Check for namespace existence before we assign the
# snat_iptables_manager
if ip_lib.network_namespace_exists(snat_ns_name):
self.snat_iptables_manager = iptables_manager.IptablesManager(
namespace=snat_ns_name,
binary_name=WRAP_NAME,
state_less=True,
use_ipv6=ipv6_utils.is_enabled_and_bind_by_default())
created = True
if self.iptables_manager is None:
# Check of namespace existence before we assign the
# iptables_manager
# NOTE(Swami): If distributed routers, all external traffic on a
# compute node will flow through the rfp interface in the router
# namespace.
if ip_lib.network_namespace_exists(self.ns_name):
self.iptables_manager = iptables_manager.IptablesManager(
namespace=self.ns_name,
binary_name=WRAP_NAME,
state_less=True,
use_ipv6=ipv6_utils.is_enabled_and_bind_by_default())
created = True
return created
class IptablesMeteringDriver(abstract_driver.MeteringAbstractDriver):
def __init__(self, plugin, conf):
self.plugin = plugin
self.conf = conf or cfg.CONF
self.routers = {}
self.driver = common_utils.load_interface_driver(self.conf)
def _update_router(self, router):
r = self.routers.get(router['id'])
if r is None:
r = RouterWithMetering(self.conf, router)
r.router = router
self.routers[r.id] = r
return r
@log_helpers.log_method_call
def update_routers(self, context, routers):
# disassociate removed routers
router_ids = set(router['id'] for router in routers)
for router_id, rm in self.routers.items():
if router_id not in router_ids:
self._process_disassociate_metering_label(rm.router)
for router in routers:
old_gw_port_id = None
old_rm = self.routers.get(router['id'])
if old_rm:
old_gw_port_id = old_rm.router['gw_port_id']
gw_port_id = router['gw_port_id']
if gw_port_id != old_gw_port_id:
if old_rm:
if router.get('distributed'):
old_rm_im = old_rm.snat_iptables_manager
else:
old_rm_im = old_rm.iptables_manager
# In case the selected manager is None pick another one.
# This is not ideal sometimes.
old_rm_im = (old_rm_im or
old_rm.snat_iptables_manager or
old_rm.iptables_manager)
if old_rm_im:
with IptablesManagerTransaction(old_rm_im):
self._process_disassociate_metering_label(router)
if gw_port_id:
self._process_associate_metering_label(router)
elif gw_port_id:
self._process_associate_metering_label(router)
@log_helpers.log_method_call
def remove_router(self, context, router_id):
if router_id in self.routers:
del self.routers[router_id]
def get_external_device_names(self, rm):
gw_port_id = rm.router.get('gw_port_id')
if not gw_port_id:
return None, None
# NOTE (Swami): External device 'qg' should be used on the
# Router namespace if the router is legacy and should be used on
# SNAT namespace if the router is distributed.
ext_dev = (EXTERNAL_DEV_PREFIX +
gw_port_id)[:self.driver.DEV_NAME_LEN]
ext_snat_dev = (ROUTER_2_FIP_DEV_PREFIX +
rm.id)[:self.driver.DEV_NAME_LEN]
return ext_dev, ext_snat_dev
def _process_metering_label_rules(self, rules, label_chain,
rules_chain, ext_dev, im):
if not ext_dev:
return
for rule in rules:
self._add_rule_to_chain(ext_dev, rule, im,
label_chain, rules_chain)
def _process_metering_label_rule_add(self, rule, ext_dev,
label_chain, rules_chain, im):
self._add_rule_to_chain(ext_dev, rule, im, label_chain, rules_chain)
def _process_metering_label_rule_delete(self, rule, ext_dev,
label_chain, rules_chain, im):
self._remove_rule_from_chain(ext_dev, rule, im,
label_chain, rules_chain)
def _add_rule_to_chain(self, ext_dev, rule, im,
label_chain, rules_chain):
ipt_rule = self._prepare_rule(ext_dev, rule, label_chain)
if rule['excluded']:
im.ipv4['filter'].add_rule(rules_chain, ipt_rule,
wrap=False, top=True)
else:
im.ipv4['filter'].add_rule(rules_chain, ipt_rule,
wrap=False, top=False)
def _remove_rule_from_chain(self, ext_dev, rule, im,
label_chain, rules_chain):
ipt_rule = self._prepare_rule(ext_dev, rule, label_chain)
if rule['excluded']:
im.ipv4['filter'].remove_rule(rules_chain, ipt_rule,
wrap=False, top=True)
else:
im.ipv4['filter'].remove_rule(rules_chain, ipt_rule,
wrap=False, top=False)
def _prepare_rule(self, ext_dev, rule, label_chain):
remote_ip = rule['remote_ip_prefix']
if rule['direction'] == 'egress':
dir_opt = '-s %s -o %s' % (remote_ip, ext_dev)
else:
dir_opt = '-d %s -i %s' % (remote_ip, ext_dev)
if rule['excluded']:
ipt_rule = '%s -j RETURN' % dir_opt
else:
ipt_rule = '%s -j %s' % (dir_opt, label_chain)
return ipt_rule
def _process_ns_specific_metering_label(self, router, ext_dev, im):
'''Process metering label based on the associated namespaces.'''
rm = self.routers.get(router['id'])
with IptablesManagerTransaction(im):
labels = router.get(constants.METERING_LABEL_KEY, [])
for label in labels:
label_id = label['id']
label_chain = iptables_manager.get_chain_name(
WRAP_NAME + LABEL + label_id, wrap=False)
rules_chain = iptables_manager.get_chain_name(
WRAP_NAME + RULE + label_id, wrap=False)
exists = rm.metering_labels.get(label_id)
if not exists:
self._create_metering_label_chain(rm,
label_chain,
rules_chain)
rm.metering_labels[label_id] = label
rules = label.get('rules')
if rules:
self._process_metering_label_rules(
rules, label_chain, rules_chain, ext_dev, im)
def _process_associate_metering_label(self, router):
self._update_router(router)
rm = self.routers.get(router['id'])
ext_dev, ext_snat_dev = self.get_external_device_names(rm)
for (im, dev) in [(rm.iptables_manager, ext_dev),
(rm.snat_iptables_manager, ext_snat_dev)]:
if im:
self._process_ns_specific_metering_label(router, dev, im)
def _process_ns_specific_disassociate_metering_label(self, router, im):
'''Disassociate metering label based on specific namespaces.'''
rm = self.routers.get(router['id'])
with IptablesManagerTransaction(im):
labels = router.get(constants.METERING_LABEL_KEY, [])
for label in labels:
label_id = label['id']
if label_id not in rm.metering_labels:
continue
label_chain = iptables_manager.get_chain_name(
WRAP_NAME + LABEL + label_id, wrap=False)
rules_chain = iptables_manager.get_chain_name(
WRAP_NAME + RULE + label_id, wrap=False)
im.ipv4['filter'].remove_chain(label_chain, wrap=False)
im.ipv4['filter'].remove_chain(rules_chain, wrap=False)
def _process_disassociate_metering_label(self, router):
rm = self.routers.get(router['id'])
if not rm:
return
for im in [rm.iptables_manager, rm.snat_iptables_manager]:
if im:
self._process_ns_specific_disassociate_metering_label(
router, im)
labels = router.get(constants.METERING_LABEL_KEY, [])
for label in labels:
label_id = label['id']
del rm.metering_labels[label_id]
@log_helpers.log_method_call
def add_metering_label(self, context, routers):
for router in routers:
self._process_associate_metering_label(router)
@log_helpers.log_method_call
def add_metering_label_rule(self, context, routers):
for router in routers:
self._add_metering_label_rule(router)
@log_helpers.log_method_call
def remove_metering_label_rule(self, context, routers):
for router in routers:
self._remove_metering_label_rule(router)
@log_helpers.log_method_call
def update_metering_label_rules(self, context, routers):
for router in routers:
self._update_metering_label_rules(router)
def _add_metering_label_rule(self, router):
self._process_metering_rule_action(router, 'create')
def _remove_metering_label_rule(self, router):
self._process_metering_rule_action(router, 'delete')
def _create_metering_label_chain(self, rm, label_chain, rules_chain):
rm.iptables_manager.ipv4['filter'].add_chain(label_chain, wrap=False)
rm.iptables_manager.ipv4['filter'].add_chain(rules_chain, wrap=False)
rm.iptables_manager.ipv4['filter'].add_rule(
TOP_CHAIN, '-j ' + rules_chain, wrap=False)
rm.iptables_manager.ipv4['filter'].add_rule(
label_chain, '', wrap=False)
def _process_metering_rule_action_based_on_ns(self, router, action,
ext_dev, im):
'''Process metering rule actions based specific namespaces.'''
rm = self.routers.get(router['id'])
with IptablesManagerTransaction(im):
labels = router.get(constants.METERING_LABEL_KEY, [])
for label in labels:
label_id = label['id']
label_chain = iptables_manager.get_chain_name(
WRAP_NAME + LABEL + label_id, wrap=False)
rules_chain = iptables_manager.get_chain_name(
WRAP_NAME + RULE + label_id, wrap=False)
exists = rm.metering_labels.get(label_id)
if action == 'create' and not exists:
self._create_metering_label_chain(rm,
label_chain,
rules_chain)
rm.metering_labels[label_id] = label
rule = label.get('rule')
if rule:
if action == 'create':
self._process_metering_label_rule_add(
rule, ext_dev, label_chain, rules_chain, im)
elif action == 'delete':
self._process_metering_label_rule_delete(
rule, ext_dev, label_chain, rules_chain, im)
def _process_metering_rule_action(self, router, action):
rm = self.routers.get(router['id'])
if not rm:
return
ext_dev, ext_snat_dev = self.get_external_device_names(rm)
for (im, dev) in [(rm.iptables_manager, ext_dev),
(rm.snat_iptables_manager, ext_snat_dev)]:
if im and dev:
self._process_metering_rule_action_based_on_ns(
router, action, dev, im)
def _update_metering_label_rules_based_on_ns(self, router, ext_dev, im):
'''Update metering lable rules based on namespace.'''
with IptablesManagerTransaction(im):
labels = router.get(constants.METERING_LABEL_KEY, [])
for label in labels:
label_id = label['id']
label_chain = iptables_manager.get_chain_name(
WRAP_NAME + LABEL + label_id, wrap=False)
rules_chain = iptables_manager.get_chain_name(
WRAP_NAME + RULE + label_id, wrap=False)
im.ipv4['filter'].empty_chain(rules_chain, wrap=False)
rules = label.get('rules')
if rules:
self._process_metering_label_rules(
rules, label_chain, rules_chain, ext_dev, im)
def _update_metering_label_rules(self, router):
rm = self.routers.get(router['id'])
if not rm:
return
ext_dev, ext_snat_dev = self.get_external_device_names(rm)
for (im, dev) in [(rm.iptables_manager, ext_dev),
(rm.snat_iptables_manager, ext_snat_dev)]:
if im and dev:
self._update_metering_label_rules_based_on_ns(router, dev, im)
@log_helpers.log_method_call
def remove_metering_label(self, context, routers):
for router in routers:
self._process_disassociate_metering_label(router)
@log_helpers.log_method_call
def get_traffic_counters(self, context, routers):
accs = {}
routers_to_reconfigure = set()
for router in routers:
rm = self.routers.get(router['id'])
if not rm:
continue
for label_id in rm.metering_labels:
try:
chain = iptables_manager.get_chain_name(WRAP_NAME +
LABEL +
label_id,
wrap=False)
chain_acc = rm.iptables_manager.get_traffic_counters(
chain, wrap=False, zero=True)
except RuntimeError:
LOG.exception('Failed to get traffic counters, '
'router: %s', router)
routers_to_reconfigure.add(router['id'])
continue
if not chain_acc:
continue
acc = accs.get(label_id, {'pkts': 0, 'bytes': 0})
acc['pkts'] += chain_acc['pkts']
acc['bytes'] += chain_acc['bytes']
accs[label_id] = acc
for router_id in routers_to_reconfigure:
del self.routers[router_id]
return accs
@log_helpers.log_method_call
def sync_router_namespaces(self, context, routers):
for router in routers:
rm = self.routers.get(router['id'])
if not rm:
continue
# NOTE(bno1): Sometimes a router is added before its namespaces are
# created. The metering agent has to periodically check if the
# namespaces for the missing iptables managers have appearead and
# create the managers for them. When a new manager is created, the
# metering rules have to be added to it.
if rm.create_iptables_managers():
self._process_associate_metering_label(router)