mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
349 lines
11 KiB
Python
Executable File
349 lines
11 KiB
Python
Executable File
#!/usr/bin/python
|
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you 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.
|
|
import sys
|
|
from merge import dataBag
|
|
from pprint import pprint
|
|
import subprocess
|
|
import logging
|
|
import re
|
|
import time
|
|
|
|
class CsHelper:
|
|
""" General helper functions
|
|
for use in the configuation process
|
|
|
|
TODO - Convert it to a module
|
|
"""
|
|
def updatefile(self, filename, val, mode):
|
|
""" add val to file """
|
|
for line in open(filename):
|
|
if line.strip().lstrip("0") == val:
|
|
return
|
|
# set the value
|
|
handle = open(filename, mode)
|
|
handle.write(val)
|
|
handle.close()
|
|
|
|
def definedinfile(self, filename, val):
|
|
""" Check if val is defined in the file """
|
|
for line in open(filename):
|
|
if re.search(val, line):
|
|
return True
|
|
return False
|
|
|
|
def addifmissing(self, filename, val):
|
|
""" Add something to a file
|
|
if it is not already there """
|
|
if not CsHelper().definedinfile(filename, val):
|
|
CsHelper().updatefile(filename, val + "\n", "a")
|
|
logging.debug("Added %s to file %s" % (val, filename))
|
|
|
|
def execute(self, command):
|
|
""" Execute command """
|
|
p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
|
|
result = p.communicate()[0]
|
|
return result.splitlines()
|
|
|
|
|
|
class CsRule:
|
|
""" Manage iprules
|
|
Supported Types:
|
|
fwmark
|
|
"""
|
|
|
|
def __init__(self, dev):
|
|
self.dev = dev
|
|
self.tableNo = dev[3]
|
|
self.table = "Table_%s" % (dev)
|
|
|
|
def addMark(self):
|
|
if not self.findMark():
|
|
cmd = "ip rule add fwmark %s table %s" % (self.tableNo, self.table)
|
|
CsHelper().execute(cmd)
|
|
logging.info("Added fwmark rule for %s" % (self.table))
|
|
|
|
def findMark(self):
|
|
srch = "from all fwmark 0x%s lookup %s" % (self.tableNo, self.table)
|
|
for i in CsHelper().execute("ip rule show"):
|
|
if srch in i.strip():
|
|
return True
|
|
return False
|
|
|
|
|
|
class csRoute:
|
|
""" Manage routes """
|
|
|
|
def __init__(self, dev):
|
|
self.dev = dev
|
|
self.tableNo = dev[3]
|
|
self.table = "Table_%s" % (dev)
|
|
|
|
def routeTable(self):
|
|
str = "%s %s" % (self.tableNo, self.table)
|
|
filename = "/etc/iproute2/rt_tables"
|
|
CsHelper().addifmissing(filename, str)
|
|
|
|
def flush(self):
|
|
CsHelper().execute("ip route flush table %s" % (self.table))
|
|
CsHelper().execute("ip route flush cache")
|
|
|
|
def add(self, address):
|
|
# ip route show dev eth1 table Table_eth1 10.0.2.0/24
|
|
# sudo ip route add default via $defaultGwIP table $tableName proto static
|
|
cmd = "dev %s table %s %s" % (self.dev, self.table, address['network'])
|
|
self.addifmissing(cmd)
|
|
|
|
def addifmissing(self, cmd):
|
|
""" Add a route is it is not already defined """
|
|
found = False
|
|
for i in CsHelper().execute("ip route show " + cmd):
|
|
found = True
|
|
if not found:
|
|
logging.info("Add " + cmd)
|
|
cmd = "ip route add " + cmd
|
|
CsHelper().execute(cmd)
|
|
|
|
|
|
class csRpsrfs:
|
|
""" Configure rpsrfs if there is more than one cpu """
|
|
|
|
def __init__(self, dev):
|
|
self.dev = dev
|
|
|
|
def enable(self):
|
|
if not self.inKernel(): return
|
|
cpus = self.cpus()
|
|
if cpus < 2: return
|
|
val = format((1 << cpus) - 1, "x")
|
|
filename = "/sys/class/net/%s/queues/rx-0/rps_cpus" % (self.dev)
|
|
CsHelper().updatefile(filename, val, "w+")
|
|
CsHelper().updatefile("/proc/sys/net/core/rps_sock_flow_entries", "256", "w+")
|
|
filename = "/sys/class/net/%s/queues/rx-0/rps_flow_cnt" % (self.dev)
|
|
CsHelper().updatefile(filename, "256", "w+")
|
|
logging.debug("rpsfr is configured for %s cpus" % (cpus))
|
|
|
|
def inKernel(self):
|
|
try:
|
|
open('/etc/rpsrfsenable')
|
|
except IOError:
|
|
logging.debug("rpsfr is not present in the kernel")
|
|
return False
|
|
else:
|
|
logging.debug("rpsfr is present in the kernel")
|
|
return True
|
|
|
|
def cpus(self):
|
|
count = 0
|
|
for line in open('/proc/cpuinfo'):
|
|
if "processor" not in line: continue
|
|
count += 1
|
|
if count < 2: logging.debug("Single CPU machine")
|
|
return count
|
|
|
|
|
|
class CsDevice:
|
|
""" Configure Network Devices """
|
|
def __init__(self, dev):
|
|
self.devlist = []
|
|
self.dev = dev
|
|
self.buildlist()
|
|
self.table = ''
|
|
self.tableNo = ''
|
|
if dev != '':
|
|
self.tableNo = dev[3]
|
|
self.table = "Table_%s" % (dev)
|
|
|
|
def buildlist(self):
|
|
"""
|
|
List all available network devices on the system
|
|
"""
|
|
self.devlist = []
|
|
for line in open('/proc/net/dev'):
|
|
vals = line.lstrip().split(':')
|
|
if (not vals[0].startswith("eth")):
|
|
continue
|
|
# Ignore control interface for now
|
|
if (vals[0] == 'eth0'):
|
|
continue
|
|
self.devlist.append(vals[0])
|
|
|
|
def set_connmark(self):
|
|
""" Set connmark for device """
|
|
if not self.has_connmark():
|
|
cmd="-A PREROUTING -i %s -m state --state NEW -j CONNMARK --set-mark %s" \
|
|
% (self.dev, self.tableNo)
|
|
CsHelper().execute("iptables -t mangle %s" % (cmd))
|
|
logging.error("Set connmark for device %s (Table %s)", self.dev, self.tableNo)
|
|
|
|
def has_connmark(self):
|
|
cmd = "iptables-save -t mangle"
|
|
for line in CsHelper().execute(cmd):
|
|
if not "PREROUTING" in line:
|
|
continue
|
|
if not "state" in line:
|
|
continue
|
|
if not "CONNMARK" in line:
|
|
continue
|
|
if not "set-xmark" in line:
|
|
continue
|
|
if not self.dev in line:
|
|
continue
|
|
return True
|
|
return False
|
|
|
|
|
|
def waitForDevice(self):
|
|
""" Wait up to 15 seconds for a device to become available """
|
|
count = 0
|
|
while count < 15:
|
|
if self.dev in self.devlist:
|
|
return True
|
|
time.sleep(1)
|
|
count += 1
|
|
self.buildlist();
|
|
logging.error("Address %s on device %s cannot be configured - device was not found", ip.ip(), self.dev)
|
|
return False
|
|
|
|
def list(self):
|
|
return self.devlist
|
|
|
|
def setUp(self):
|
|
""" Ensure device is up """
|
|
cmd = "ip link show %s | grep 'state DOWN'" % (self.dev)
|
|
for i in CsHelper().execute(cmd):
|
|
if " DOWN " in i:
|
|
cmd2 = "ip link set %s up" % (self.dev)
|
|
CsHelper().execute(cmd2)
|
|
self.set_connmark()
|
|
|
|
|
|
class CsIP:
|
|
|
|
def __init__(self, dev):
|
|
self.dev = dev
|
|
self.iplist = {}
|
|
self.address = {}
|
|
self.list()
|
|
|
|
def setAddress(self, address):
|
|
self.address = address
|
|
|
|
|
|
def configure(self):
|
|
logging.info("Configuring address %s on device %s", self.ip(), self.dev)
|
|
cmd = "ip addr add dev %s %s brd +" % (self.dev, self.ip())
|
|
subprocess.call(cmd, shell=True)
|
|
self.post_configure()
|
|
|
|
def post_configure(self):
|
|
""" The steps that must be done after a device is configured """
|
|
route = csRoute(self.dev)
|
|
route.routeTable()
|
|
CsRule(self.dev).addMark()
|
|
CsDevice(self.dev).setUp()
|
|
self.arpPing()
|
|
route.add(self.address)
|
|
csRpsrfs(self.dev).enable()
|
|
route.flush()
|
|
|
|
def list(self):
|
|
self.iplist = {}
|
|
cmd = ("ip addr show dev " + self.dev)
|
|
for i in CsHelper().execute(cmd):
|
|
vals = i.lstrip().split()
|
|
if (vals[0] == 'inet'):
|
|
self.iplist[vals[1]] = self.dev
|
|
|
|
def configured(self):
|
|
dev = self.address['device']
|
|
if (self.address['cidr'] in self.iplist.keys()):
|
|
return True
|
|
return False
|
|
|
|
def ip(self):
|
|
return str(self.address['cidr'])
|
|
|
|
def hasIP(self, ip):
|
|
return ip in self.address.values()
|
|
|
|
def arpPing(self):
|
|
cmd = "arping -c 1 -I %s -A -U -s %s %s" % (self.dev, self.address['public_ip'], self.address['public_ip'])
|
|
CsHelper().execute(cmd)
|
|
|
|
# Delete any ips that are configured but not in the bag
|
|
def compare(self, bag):
|
|
if (len(self.iplist) > 0 and not self.dev in bag.keys()):
|
|
# Remove all IPs on this device
|
|
logging.info("Will remove all configured addresses on device %s", self.dev)
|
|
self.delete("all")
|
|
return False
|
|
for ip in self.iplist:
|
|
found = False
|
|
for address in bag[self.dev]:
|
|
self.setAddress(address)
|
|
if (self.hasIP(ip)):
|
|
found = True
|
|
if (not found):
|
|
self.delete(ip)
|
|
|
|
def delete(self, ip):
|
|
remove = []
|
|
if (ip == "all"):
|
|
logging.info("Removing addresses from device %s", self.dev)
|
|
remove = self.iplist.keys()
|
|
else:
|
|
remove.append(ip)
|
|
for ip in remove:
|
|
cmd = "ip addr del dev %s %s" % (self.dev, ip)
|
|
subprocess.call(cmd, shell=True)
|
|
logging.info("Removed address %s from device %s", ip, self.dev)
|
|
|
|
|
|
def main(argv):
|
|
logging.basicConfig(filename='/var/log/cloud.log',
|
|
level=logging.DEBUG, format='%(asctime)s %(message)s')
|
|
|
|
db = dataBag()
|
|
db.setKey("ips")
|
|
db.load()
|
|
dbag = db.getDataBag()
|
|
for dev in CsDevice('').list():
|
|
ip = CsIP(dev)
|
|
ip.compare(dbag)
|
|
|
|
for dev in dbag:
|
|
if dev == "id":
|
|
continue
|
|
if dev == "eth0":
|
|
continue
|
|
ip = CsIP(dev)
|
|
for address in dbag[dev]:
|
|
csRoute(dev).add(address)
|
|
ip.setAddress(address)
|
|
if ip.configured():
|
|
logging.info("Address %s on device %s already configured", ip.ip(), dev)
|
|
ip.post_configure()
|
|
else:
|
|
logging.info("Address %s on device %s not configured", ip.ip(), dev)
|
|
if CsDevice(dev).waitForDevice():
|
|
ip.configure()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv)
|