# Copyright 2016 OVH SAS # # 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_log import helpers as log_helpers from oslo_log import log from neutron.agent.l2.extensions import qos_linux as qos from neutron.agent.linux import iptables_manager from neutron.agent.linux import tc_lib import neutron.common.constants as const from neutron.services.qos.drivers.linuxbridge import driver LOG = log.getLogger(__name__) class QosLinuxbridgeAgentDriver(qos.QosLinuxAgentDriver): # TODO(ralonsoh): # - All driver calls should include the rule parameter, including # the delete function, to have the 'direction' parameter. This QoS # extension modification is going to be implemented in # https://review.openstack.org/#/c/341186/ SUPPORTED_RULES = driver.SUPPORTED_RULES IPTABLES_DIRECTION = {const.INGRESS_DIRECTION: 'physdev-out', const.EGRESS_DIRECTION: 'physdev-in'} IPTABLES_DIRECTION_PREFIX = {const.INGRESS_DIRECTION: "i", const.EGRESS_DIRECTION: "o"} def initialize(self): LOG.info("Initializing Linux bridge QoS extension") self.iptables_manager = iptables_manager.IptablesManager(use_ipv6=True) self.tbf_latency = cfg.CONF.QOS.tbf_latency def _dscp_chain_name(self, direction, device): return iptables_manager.get_chain_name( "qos-%s%s" % (self.IPTABLES_DIRECTION_PREFIX[direction], device[3:])) def _dscp_rule(self, direction, device): return ('-m physdev --%s %s --physdev-is-bridged ' '-j $%s') % (self.IPTABLES_DIRECTION[direction], device, self._dscp_chain_name(direction, device)) def _dscp_rule_tag(self, device): return "dscp-%s" % device @log_helpers.log_method_call def create_bandwidth_limit(self, port, rule): tc_wrapper = self._get_tc_wrapper(port) if rule.direction == const.INGRESS_DIRECTION: tc_wrapper.set_tbf_bw_limit( rule.max_kbps, rule.max_burst_kbps, self.tbf_latency) else: tc_wrapper.set_filters_bw_limit( rule.max_kbps, self._get_egress_burst_value(rule) ) @log_helpers.log_method_call def update_bandwidth_limit(self, port, rule): tc_wrapper = self._get_tc_wrapper(port) if rule.direction == const.INGRESS_DIRECTION: tc_wrapper.update_tbf_bw_limit( rule.max_kbps, rule.max_burst_kbps, self.tbf_latency) else: tc_wrapper.update_filters_bw_limit( rule.max_kbps, self._get_egress_burst_value(rule) ) @log_helpers.log_method_call def delete_bandwidth_limit(self, port): tc_wrapper = self._get_tc_wrapper(port) tc_wrapper.delete_filters_bw_limit() @log_helpers.log_method_call def delete_bandwidth_limit_ingress(self, port): tc_wrapper = self._get_tc_wrapper(port) tc_wrapper.delete_tbf_bw_limit() @log_helpers.log_method_call def create_dscp_marking(self, port, rule): with self.iptables_manager.defer_apply(): self._set_outgoing_qos_chain_for_port(port) self._set_dscp_mark_rule(port, rule.dscp_mark) @log_helpers.log_method_call def update_dscp_marking(self, port, rule): with self.iptables_manager.defer_apply(): self._delete_dscp_mark_rule(port) self._set_outgoing_qos_chain_for_port(port) self._set_dscp_mark_rule(port, rule.dscp_mark) @log_helpers.log_method_call def delete_dscp_marking(self, port): with self.iptables_manager.defer_apply(): self._delete_dscp_mark_rule(port) self._delete_outgoing_qos_chain_for_port(port) def _set_outgoing_qos_chain_for_port(self, port): chain_name = self._dscp_chain_name( const.EGRESS_DIRECTION, port['device']) chain_rule = self._dscp_rule( const.EGRESS_DIRECTION, port['device']) self.iptables_manager.ipv4['mangle'].add_chain(chain_name) self.iptables_manager.ipv6['mangle'].add_chain(chain_name) self.iptables_manager.ipv4['mangle'].add_rule('POSTROUTING', chain_rule) self.iptables_manager.ipv6['mangle'].add_rule('POSTROUTING', chain_rule) def _delete_outgoing_qos_chain_for_port(self, port): chain_name = self._dscp_chain_name( const.EGRESS_DIRECTION, port['device']) chain_rule = self._dscp_rule( const.EGRESS_DIRECTION, port['device']) if self._qos_chain_is_empty(port, 4): self.iptables_manager.ipv4['mangle'].remove_chain(chain_name) self.iptables_manager.ipv4['mangle'].remove_rule('POSTROUTING', chain_rule) if self._qos_chain_is_empty(port, 6): self.iptables_manager.ipv6['mangle'].remove_chain(chain_name) self.iptables_manager.ipv6['mangle'].remove_rule('POSTROUTING', chain_rule) def _set_dscp_mark_rule(self, port, dscp_value): chain_name = self._dscp_chain_name( const.EGRESS_DIRECTION, port['device']) rule = "-j DSCP --set-dscp %s" % dscp_value self.iptables_manager.ipv4['mangle'].add_rule( chain_name, rule, tag=self._dscp_rule_tag(port['device'])) self.iptables_manager.ipv6['mangle'].add_rule( chain_name, rule, tag=self._dscp_rule_tag(port['device'])) def _delete_dscp_mark_rule(self, port): self.iptables_manager.ipv4['mangle'].clear_rules_by_tag( self._dscp_rule_tag(port['device'])) self.iptables_manager.ipv6['mangle'].clear_rules_by_tag( self._dscp_rule_tag(port['device'])) def _qos_chain_is_empty(self, port, ip_version=4): chain_name = self._dscp_chain_name( const.EGRESS_DIRECTION, port['device']) rules_in_chain = self.iptables_manager.get_chain( "mangle", chain_name, ip_version=ip_version) return len(rules_in_chain) == 0 def _get_tc_wrapper(self, port): return tc_lib.TcCommand( port['device'], cfg.CONF.QOS.kernel_hz, )