Skip to content

Commit 8a23913

Browse files
Ian Southamwilderrodrigues
authored andcommitted
Getting redundancy to work via teh configuration files
Some refactoring to make class loading a little more logical Removed fw global (does not persist accross modules) Added first tests Added command line call to set and disable redundancy Added command line call to set master
1 parent 093749e commit 8a23913

File tree

14 files changed

+790
-588
lines changed

14 files changed

+790
-588
lines changed
Lines changed: 397 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
# -- coding: utf-8 --
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
from CsDatabag import CsDataBag, CsCmdLine
19+
from CsApp import CsApache, CsDnsmasq, CsPasswdSvc
20+
import CsHelper
21+
import logging
22+
import CsHelper
23+
import subprocess
24+
from CsRoute import CsRoute
25+
from CsRule import CsRule
26+
27+
class CsAddress(CsDataBag):
28+
29+
def compare(self):
30+
for dev in CsDevice('', self.fw).list():
31+
ip = CsIP(dev, self.fw)
32+
ip.compare(self.dbag)
33+
34+
def get_ips(self):
35+
ret = []
36+
for dev in self.dbag:
37+
if dev == "id":
38+
continue
39+
for ip in self.dbag[dev]:
40+
ret.append(CsInterface(ip))
41+
return ret
42+
43+
def needs_vrrp(self,o):
44+
"""
45+
Returns if the ip needs to be managed by keepalived or not
46+
"""
47+
if "nw_type" in o and o['nw_type'] in [ 'guest' ]:
48+
return True
49+
return False
50+
51+
def get_control_if(self):
52+
"""
53+
Return the address object that has the control interface
54+
"""
55+
for ip in self.get_ips():
56+
if ip.is_control():
57+
return ip
58+
return None
59+
60+
def process(self):
61+
for dev in self.dbag:
62+
if dev == "id":
63+
continue
64+
ip = CsIP(dev, self.fw)
65+
addcnt = 0
66+
for address in self.dbag[dev]:
67+
if not address["nw_type"] == "control":
68+
CsRoute(dev).add(address)
69+
ip.setAddress(address)
70+
if ip.configured():
71+
logging.info("Address %s on device %s already configured", ip.ip(), dev)
72+
ip.post_configure()
73+
else:
74+
logging.info("Address %s on device %s not configured", ip.ip(), dev)
75+
if CsDevice(dev, self.fw).waitfordevice():
76+
ip.configure()
77+
# This could go one level up but the ip type is stored in the
78+
# ip address object and not in the device object
79+
# Call only once
80+
if addcnt == 0:
81+
self.add_netstats(address)
82+
addcnt += 1
83+
84+
def add_netstats(self, address):
85+
# add in the network stats iptables rules
86+
dev = "eth%s" % address['nic_dev_id']
87+
if address["nw_type"] == "public_ip":
88+
self.fw.append(["", "front", "-A FORWARD -j NETWORK_STATS"])
89+
self.fw.append(["", "front", "-A INPUT -j NETWORK_STATS"])
90+
self.fw.append(["", "front", "-A OUTPUT -j NETWORK_STATS"])
91+
# it is not possible to calculate these devices
92+
# When the vrouter and the vpc router are combined this silliness can go
93+
self.fw.append(["", "", "-A NETWORK_STATS -i %s -o eth0 -p tcp" % dev])
94+
self.fw.append(["", "", "-A NETWORK_STATS -o %s -i eth0 -p tcp" % dev])
95+
self.fw.append(["", "", "-A NETWORK_STATS -o %s ! -i eth0 -p tcp" % dev])
96+
self.fw.append(["", "", "-A NETWORK_STATS -i %s ! -o eth0 -p tcp" % dev])
97+
98+
if address["nw_type"] == "guest":
99+
self.fw.append(["", "front", "-A FORWARD -j NETWORK_STATS_%s" % dev])
100+
self.fw.append(["", "front", "-A NETWORK_STATS_%s -o %s -s %s" % (dev, dev, address['network'])])
101+
self.fw.append(["", "front", "-A NETWORK_STATS_%s -o %s -d %s" % (dev, dev, address['network'])])
102+
# Only relevant if there is a VPN configured so will have to move
103+
# at some stage
104+
self.fw.append(["mangle", "", "-A FORWARD -j VPN_STATS_%s" % dev])
105+
self.fw.append(["mangle", "", "-A VPN_STATS_%s -o %s -m mark --set-xmark 0x525/0xffffffff" % (dev, dev)])
106+
self.fw.append(["mangle", "", "-A VPN_STATS_%s -i %s -m mark --set-xmark 0x524/0xffffffff" % (dev, dev)])
107+
108+
class CsInterface:
109+
""" Hold one single ip """
110+
def __init__(self, o):
111+
self.address = o
112+
113+
def get_ip(self):
114+
return self.get_attr("public_ip")
115+
116+
def get_device(self):
117+
return self.get_attr("device")
118+
119+
def get_cidr(self):
120+
return self.get_attr("cidr")
121+
122+
def get_broadcast(self):
123+
return self.get_attr("broadcast")
124+
125+
def get_attr(self, attr):
126+
if attr in self.address:
127+
return self.address[attr]
128+
else:
129+
return "ERROR"
130+
131+
def needs_vrrp(self):
132+
"""
133+
Returns if the ip needs to be managed by keepalived or not
134+
"""
135+
if "nw_type" in self.address and self.address['nw_type'] in [ 'guest' ]:
136+
return True
137+
return False
138+
139+
def is_control(self):
140+
if "nw_type" in self.address and self.address['nw_type'] in [ 'control' ]:
141+
return True
142+
return False
143+
144+
def to_str(self):
145+
pprint(self.address)
146+
147+
class CsDevice:
148+
""" Configure Network Devices """
149+
def __init__(self, dev, fw):
150+
self.devlist = []
151+
self.dev = dev
152+
self.buildlist()
153+
self.table = ''
154+
self.tableNo = ''
155+
if dev != '':
156+
self.tableNo = dev[3]
157+
self.table = "Table_%s" % dev
158+
self.fw = fw
159+
160+
def configure_rp(self):
161+
"""
162+
Configure Reverse Path Filtering
163+
"""
164+
filename = "/proc/sys/net/ipv4/conf/%s/rp_filter" % self.dev
165+
CsHelper.updatefile(filename, "1\n", "w")
166+
167+
def buildlist(self):
168+
"""
169+
List all available network devices on the system
170+
"""
171+
self.devlist = []
172+
for line in open('/proc/net/dev'):
173+
vals = line.lstrip().split(':')
174+
if (not vals[0].startswith("eth")):
175+
continue
176+
self.devlist.append(vals[0])
177+
178+
179+
def waitfordevice(self):
180+
""" Wait up to 15 seconds for a device to become available """
181+
count = 0
182+
while count < 15:
183+
if self.dev in self.devlist:
184+
return True
185+
time.sleep(1)
186+
count += 1
187+
self.buildlist();
188+
logging.error("Device %s cannot be configured - device was not found", self.dev)
189+
return False
190+
191+
def list(self):
192+
return self.devlist
193+
194+
def setUp(self):
195+
""" Ensure device is up """
196+
cmd = "ip link show %s | grep 'state DOWN'" % self.dev
197+
for i in CsHelper.execute(cmd):
198+
if " DOWN " in i:
199+
cmd2 = "ip link set %s up" % self.dev
200+
CsHelper.execute(cmd2)
201+
cmd = "-A PREROUTING -i %s -m state --state NEW -j CONNMARK --set-xmark 0x%s/0xffffffff" % \
202+
(self.dev, self.dev[3])
203+
self.fw.append(["mangle", "", cmd])
204+
205+
206+
class CsIP:
207+
208+
def __init__(self, dev, fw):
209+
self.dev = dev
210+
self.iplist = {}
211+
self.address = {}
212+
self.list()
213+
self.fw = fw
214+
215+
def setAddress(self, address):
216+
self.address = address
217+
218+
def getAddress(self):
219+
return self.address
220+
221+
def configure(self):
222+
logging.info("Configuring address %s on device %s", self.ip(), self.dev)
223+
cmd = "ip addr add dev %s %s brd +" % (self.dev, self.ip())
224+
subprocess.call(cmd, shell=True)
225+
self.post_configure()
226+
227+
def post_configure(self):
228+
""" The steps that must be done after a device is configured """
229+
if not self.get_type() in [ "control" ]:
230+
route = CsRoute(self.dev)
231+
route.routeTable()
232+
CsRule(self.dev).addMark()
233+
CsDevice(self.dev, self.fw).setUp()
234+
self.arpPing()
235+
CsRpsrfs(self.dev).enable()
236+
self.post_config_change("add")
237+
238+
def get_type(self):
239+
""" Return the type of the IP
240+
guest
241+
control
242+
public
243+
"""
244+
if "nw_type" in self.address:
245+
return self.address['nw_type']
246+
return "unknown"
247+
248+
def get_ip_address(self):
249+
"""
250+
Return ip address if known
251+
"""
252+
if "public_ip" in self.address:
253+
return self.address['public_ip']
254+
return "unknown"
255+
256+
def post_config_change(self, method):
257+
route = CsRoute(self.dev)
258+
route.routeTable()
259+
route.add(self.address, method)
260+
# On deletion nw_type will no longer be known
261+
if self.get_type() in [ "guest" ]:
262+
devChain = "ACL_INBOUND_%s" % (self.dev)
263+
CsDevice(self.dev, self.fw).configure_rp()
264+
265+
self.fw.append(["nat", "front",
266+
"-A POSTROUTING -s %s -o %s -j SNAT --to-source %s" % \
267+
(self.address['network'], self.dev, self.address['public_ip'])
268+
])
269+
self.fw.append(["mangle", "front", "-A %s -j ACCEPT" % devChain])
270+
271+
self.fw.append(["", "front",
272+
"-A FORWARD -o %s -d %s -j %s" % (self.dev, self.address['network'], devChain)
273+
])
274+
self.fw.append(["", "", "-A %s -j DROP" % devChain])
275+
self.fw.append(["mangle", "",
276+
"-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j %s" % \
277+
(self.dev, self.address['network'], self.address['public_ip'], devChain)
278+
])
279+
dns = CsDnsmasq(self)
280+
dns.add_firewall_rules()
281+
app = CsApache(self)
282+
app.setup()
283+
pwdsvc = CsPasswdSvc(self).setup()
284+
elif self.get_type() == "public":
285+
if self.address["source_nat"] == True:
286+
cmdline = CsDataBag("cmdline", self.fw)
287+
dbag = cmdline.get_bag()
288+
type = dbag["config"]["type"]
289+
if type == "vpcrouter":
290+
vpccidr = dbag["config"]["vpccidr"]
291+
self.fw.append(["filter", "", "-A FORWARD -s %s ! -d %s -j ACCEPT" % (vpccidr, vpccidr)])
292+
self.fw.append(["nat","","-A POSTROUTING -j SNAT -o %s --to-source %s" % (self.dev, self.address['public_ip'])])
293+
elif type == "router":
294+
logging.error("Not able to setup sourcenat for a regular router yet")
295+
else:
296+
logging.error("Unable to process source nat configuration for router of type %s" % type)
297+
route.flush()
298+
299+
def list(self):
300+
self.iplist = {}
301+
cmd = ("ip addr show dev " + self.dev)
302+
for i in CsHelper.execute(cmd):
303+
vals = i.lstrip().split()
304+
if (vals[0] == 'inet'):
305+
self.iplist[vals[1]] = self.dev
306+
307+
def configured(self):
308+
if self.address['cidr'] in self.iplist.keys():
309+
return True
310+
return False
311+
312+
def ip(self):
313+
return str(self.address['cidr'])
314+
315+
def getDevice(self):
316+
return self.dev
317+
318+
def hasIP(self, ip):
319+
return ip in self.address.values()
320+
321+
def arpPing(self):
322+
cmd = "arping -c 1 -I %s -A -U -s %s %s" % (self.dev, self.address['public_ip'], self.address['public_ip'])
323+
CsHelper.execute(cmd)
324+
325+
# Delete any ips that are configured but not in the bag
326+
def compare(self, bag):
327+
if len(self.iplist) > 0 and (not self.dev in bag.keys() or len(bag[self.dev]) == 0):
328+
# Remove all IPs on this device
329+
logging.info("Will remove all configured addresses on device %s", self.dev)
330+
self.delete("all")
331+
app = CsApache(self)
332+
app.remove()
333+
334+
# This condition should not really happen but did :)
335+
# It means an apache file got orphaned after a guest network address was deleted
336+
if len(self.iplist) == 0 and (not self.dev in bag.keys() or len(bag[self.dev]) == 0):
337+
app = CsApache(self)
338+
app.remove()
339+
340+
for ip in self.iplist:
341+
found = False
342+
if self.dev in bag.keys():
343+
for address in bag[self.dev]:
344+
self.setAddress(address)
345+
if self.hasIP(ip):
346+
found = True
347+
if not found:
348+
self.delete(ip)
349+
350+
def delete(self, ip):
351+
remove = []
352+
if ip == "all":
353+
logging.info("Removing addresses from device %s", self.dev)
354+
remove = self.iplist.keys()
355+
else:
356+
remove.append(ip)
357+
for ip in remove:
358+
cmd = "ip addr del dev %s %s" % (self.dev, ip)
359+
subprocess.call(cmd, shell=True)
360+
logging.info("Removed address %s from device %s", ip, self.dev)
361+
self.post_config_change("delete")
362+
363+
class CsRpsrfs:
364+
""" Configure rpsrfs if there is more than one cpu """
365+
366+
def __init__(self, dev):
367+
self.dev = dev
368+
369+
def enable(self):
370+
if not self.inKernel(): return
371+
cpus = self.cpus()
372+
if cpus < 2: return
373+
val = format((1 << cpus) - 1, "x")
374+
filename = "/sys/class/net/%s/queues/rx-0/rps_cpus" % (self.dev)
375+
CsHelper.updatefile(filename, val, "w+")
376+
CsHelper.updatefile("/proc/sys/net/core/rps_sock_flow_entries", "256", "w+")
377+
filename = "/sys/class/net/%s/queues/rx-0/rps_flow_cnt" % (self.dev)
378+
CsHelper.updatefile(filename, "256", "w+")
379+
logging.debug("rpsfr is configured for %s cpus" % (cpus))
380+
381+
def inKernel(self):
382+
try:
383+
open('/etc/rpsrfsenable')
384+
except IOError:
385+
logging.debug("rpsfr is not present in the kernel")
386+
return False
387+
else:
388+
logging.debug("rpsfr is present in the kernel")
389+
return True
390+
391+
def cpus(self):
392+
count = 0
393+
for line in open('/proc/cpuinfo'):
394+
if "processor" not in line: continue
395+
count += 1
396+
if count < 2: logging.debug("Single CPU machine")
397+
return count

0 commit comments

Comments
 (0)