|
#!/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()) |