Skip to content

Instantly share code, notes, and snippets.

@hichihara
Last active July 29, 2016 07:52
Show Gist options
  • Save hichihara/3135411a6b4793b75b4e25438f2e4b89 to your computer and use it in GitHub Desktop.
Save hichihara/3135411a6b4793b75b4e25438f2e4b89 to your computer and use it in GitHub Desktop.
[PoC] Set qos for VM ports to NIC

Set qos for VM ports to NIC

Precondition

  • Plugin: ML2
  • Driver: Openvswitch
  • Network: VLAN

Prepare

  • Download devstack
  • Set configures to local.conf.
  • Run devstack
  • Execute ". openrc admin admin" command in devstack directory because this script need admin privilege.

How to use

Run the script on Compute node which VMs are allocated. Script outputs port info:

set-qos.py -v

Script set qos to NIC:

set-qos.py eth2:100000 <port-id1>:10000 <port-id2>:20000
#!/usr/bin/python
import netaddr
import os
import socket
import sys
from neutronclient.neutron import client
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils as linux_utils
class PortInfo(object):
def __init__(self, port_id, net_type, seg_id, mac_address, min_rate,
flowid):
self.port_id = port_id
self.net_type = net_type
self.seg_id = seg_id
self.mac_address = mac_address
self.min_rate = min_rate
self.flowid = flowid
class TcManager(object):
"""Wrapper for linux tc.
Keeps track of tc setting of a physical interface.
"""
def __init__(self, device, rate, execute=None, namespace=None):
self.execute = execute or linux_utils.execute
self.namespace = namespace
self.phys_int = device
self.max_rate = rate
self.ports = {}
self.next_flowid = 2
def add_port(self, port_id, net_type, seg_id, mac_address, min_rate):
self.ports[port_id] = PortInfo(port_id, net_type, seg_id,
mac_address, min_rate, self.next_flowid)
self.next_flowid += 1
def del_port(self, port_id):
if port_id in self.ports:
del self.ports[port_id]
def vlan_port_filter(self, port):
mac = int(netaddr.EUI(port.mac_address))
mac1 = mac >> 16
mac2 = 0xffff & mac
cmd = ['basic', 'match',
'meta(vlan mask 0xfff eq 0x%x)' % port.seg_id, 'and',
'u32(u32 0x%x 0xffffffff at -8)' % mac1, 'and',
'u32(u16 0x%x 0xffff at -4)' % mac2,
'flowid', '1:%d' % port.flowid]
return cmd
def vxlan_port_filter(self, port):
seg_id = int(port.seg_id)
seg_id1 = seg_id >> 48
seg_id2 = 0xffffffff & (seg_id >> 16)
seg_id3 = 0xffff & seg_id
mac = int(netaddr.EUI(port.mac_address))
mac1 = mac >> 32
mac2 = 0xffffffff & mac
cmd = ['u32',
'match', 'u16', '0x%x' % seg_id1, '0xffff', 'at', '28',
'match', 'u32', '0x%x' % seg_id2, '0xffffffff', 'at', '32',
'match', 'u16', '0x%x' % seg_id3, '0xffff', 'at', '36',
'match', 'u16', '0x%x' % mac1, '0xffff', 'at', '42',
'match', 'u32', '0x%x' % mac2, '0xffffffff', 'at', '44',
'flowid', '1:%d' % port.flowid]
return cmd
def port_filter(self, port):
if port.net_type == 'vlan':
return self.vlan_port_filter(port)
else:
return self.vxlan_port_filter(port)
def apply(self):
cmd = ['tc', 'qdisc', 'del', 'dev', self.phys_int,
'root', 'handle', '1:']
self.execute(cmd, check_exit_code=False, run_as_root=True)
cmd = ['tc', 'qdisc', 'replace', 'dev', self.phys_int,
'root', 'handle', '1:', 'htb', 'default', '1']
self.execute(cmd, run_as_root=True)
cmd = ['tc', 'class', 'add', 'dev', self.phys_int,
'parent', '1:', 'classid', '1:fffe', 'htb', 'rate',
'%dkbit' % self.max_rate]
self.execute(cmd, run_as_root=True)
cmd = ['tc', 'class', 'add', 'dev', self.phys_int,
'parent', '1:fffe', 'classid', '1:1', 'htb',
'rate', '1000kbit', 'ceil', '%dkbit' % self.max_rate]
self.execute(cmd, run_as_root=True)
for port in self.ports.values():
cmd = ['tc', 'class', 'add', 'dev', self.phys_int,
'parent', '1:fffe', 'classid', '1:%d' % port.flowid,
'htb', 'rate', '%dkbit' % port.min_rate,
'ceil', '%dkbit' % port.min_rate]
self.execute(cmd, run_as_root=True)
cmd = ['tc', 'filter', 'add', 'dev', self.phys_int,
'protocol', 'ip', 'parent', '1:0', 'prio', '1']
cmd += self.port_filter(port)
self.execute(cmd, run_as_root=True)
def get_ports_with_nic():
username = os.getenv('OS_USERNAME')
password = os.getenv('OS_PASSWORD')
tenant_name = os.getenv('OS_TENANT_NAME')
auth_url = os.getenv('OS_AUTH_URL')
if not username or not password or not tenant_name or not auth_url:
raise Exception('must set OS_* environment variables')
if username != 'admin':
raise Exception('must be admin user')
n_client = client.Client('2.0', username=username, password=password,
tenant_name=tenant_name, auth_url=auth_url)
hostname = socket.gethostname()
filters = {'host': [hostname]}
agents = n_client.list_agents(**filters)['agents']
l2_agent = None
for agent in agents:
if agent['agent_type'] in ['Open vSwitch agent', 'Linux bridge agent']:
l2_agent = agent
break
if not l2_agent:
raise Exception('no l2-agent running')
l2_agent_config = l2_agent['configurations']
tunneling_ip = l2_agent_config.get('tunneling_ip')
tunneling_nic = (ip_lib.IPWrapper().get_device_by_ip(tunneling_ip)
if tunneling_ip else None)
bridge_mappings = l2_agent_config.get('bridge_mappings', {})
interface_mappings = l2_agent_config.get('interface_mappings', {})
filters = {'binding:host_id': [hostname]}
ports = n_client.list_ports(**filters)['ports']
net_ids = {port['network_id'] for port in ports}
filters = {'id': list(net_ids)}
nets = n_client.list_networks(**filters)['networks']
net_info = {}
for net in nets:
network_type = net['provider:network_type']
nic = None
if network_type == 'vxlan':
nic = tunneling_nic
elif network_type == 'vlan':
phys_net = net['provider:physical_network']
br = bridge_mappings.get(phys_net)
if br:
# only this part is heuristic
if br.startswith('br-'):
nic = br[3:]
elif br.startswith('br'):
nic = br[2:]
else:
nic = interface_mappings.get(phys_net)
if nic:
net_info[net['id']] = \
(nic, network_type, net['provider:segmentation_id'])
port_info = {}
for port in ports:
ni = net_info.get(port['network_id'])
if ni:
port_info[port['id']] = (port['mac_address'], ni[0], ni[1], ni[2])
return port_info
def usage():
print "usage: set_qos [-v] phys_nic:rate port:rate ..."
print " -v: print port info on the host"
print " phys_nic:rate: specify nic set QoS and its bandwidth(Kbit)"
print " mandatory"
print " port:rate: specify port is and its minimum bandwitch(Kbit)"
print " specify at least one, can be repeated"
os._exit(1)
def test_execute(cmd, **kwargs):
print cmd
def main():
vflag = False
port_rate = []
for arg in sys.argv[1:]:
if arg == '-v':
vflag = True
continue
try:
port, rate = arg.split(':')
port_rate.append((port, int(rate)))
except:
usage()
port_info = get_ports_with_nic()
if vflag:
for port_id in port_info:
mac, nic, net_type, seg_id = port_info[port_id]
print port_id, mac, nic, net_type, seg_id
if not port_rate:
return
if len(port_rate) <= 1:
usage()
phys_nic, phys_rate = port_rate[0]
port_rate = port_rate[1:]
for port, rate in port_rate:
if port not in port_info:
raise Exception("port %s is not on this host" % port)
nic = port_info[port][1]
if nic != phys_nic:
raise Exception("phys nic %s is not used by port %s" %
(phys_nic, port))
print "setup info:"
print "physical nic, physical bandwidth: %s %sKbit" % (phys_nic, phys_rate)
print "port id, minimum bandwidth::"
for port, rate in port_rate:
print " %s %sKbit" % (port, rate)
#tc_manager = TcManager(phys_nic, phys_rate, execute=test_execute)
tc_manager = TcManager(phys_nic, phys_rate)
for port, rate in port_rate:
mac, nic, net_type, seg_id = port_info[port]
tc_manager.add_port(port, net_type, seg_id, mac, rate)
tc_manager.apply()
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment