Dhcp refactor

Loads of tiny bugs squashed and some big ones
Tested with domR needs VPC testing now
TODO:  Unit tests CsDhcp
This commit is contained in:
Ian Southam 2014-12-05 17:42:03 +01:00 committed by wilderrodrigues
parent 31266d354f
commit 8a6a407114
6 changed files with 147 additions and 127 deletions

View File

@ -141,18 +141,19 @@ class CsAcl(CsDataBag):
class AclDevice():
""" A little class for each list of acls per device """
def __init__(self, obj, fw):
def __init__(self, obj, config):
self.ingess = []
self.egress = []
self.device = obj['device']
self.ip = obj['nic_ip']
self.netmask = obj['nic_netmask']
self.config = config
self.cidr = "%s/%s" % (self.ip, self.netmask)
if "ingress_rules" in obj.keys():
self.ingress = obj['ingress_rules']
if "egress_rules" in obj.keys():
self.egress = obj['egress_rules']
self.fw = fw
self.fw = config.get_fw()
def create(self):
self.process("ingress", self.ingress)
@ -166,10 +167,8 @@ class CsAcl(CsDataBag):
class AclRule():
def __init__(self, direction, acl, rule, config):
if config_is_vpc():
self.init_vpc(self, direction, acl, rule, config)
else:
self.init_vr(self, direction, acl, rule, config)
if config.is_vpc():
self.init_vpc(direction, acl, rule, config)
def init_vpc(self, direction, acl, rule, config):
self.table = ""
@ -179,7 +178,7 @@ class CsAcl(CsDataBag):
self.dest = "-s %s" % rule['cidr']
if direction == "egress":
self.table = config.get_egress_table()
self.chain = config.get_egress_chain(self.device, ip)
self.chain = config.get_egress_chain(self.device, acl.ip)
self.dest = "-d %s" % rule['cidr']
self.type = ""
self.type = rule['type']
@ -217,9 +216,9 @@ class CsAcl(CsDataBag):
if item == "id":
continue
if self.config.is_vpc():
dev_obj = self.AclDevice(self.dbag[item], self.fw).create()
dev_obj = self.AclDevice(self.dbag[item], self.config).create()
else:
self.AclIP(self.dbag[item], self.fw).create()
self.AclIP(self.dbag[item], self.config).create()
class CsVmMetadata(CsDataBag):

View File

@ -20,7 +20,9 @@ from CsApp import CsApache, CsDnsmasq, CsPasswdSvc
import CsHelper
import logging
import CsHelper
import subprocess
import time
from CsRoute import CsRoute
from CsRule import CsRule
@ -53,6 +55,26 @@ class CsAddress(CsDataBag):
return ip.get_ip()
return None
def get_guest_gateway(self):
"""
Return the gateway of the first guest interface
For use with routers not vpcrouters
"""
for ip in self.get_ips():
if ip.is_guest():
return ip.get_gateway()
return None
def get_guest_netmask(self):
"""
Return the gateway of the first guest interface
For use with routers not vpcrouters
"""
for ip in self.get_ips():
if ip.is_guest():
return ip.get_netmask()
return None
def needs_vrrp(self, o):
"""
Returns if the ip needs to be managed by keepalived or not
@ -128,6 +150,12 @@ class CsInterface:
def get_ip(self):
return self.get_attr("public_ip")
def get_netmask(self):
return self.get_attr("netmask")
def get_gateway(self):
return self.get_attr("gateway")
def get_device(self):
return self.get_attr("device")

View File

@ -65,6 +65,9 @@ class CsConfig(object):
def get_domain(self):
return self.cl.get_domain()
def get_dns(self):
return self.get_cmdline().get_dns()
def get_format(self):
return self.__LOG_FORMAT

View File

@ -62,6 +62,14 @@ class CsCmdLine(CsDataBag):
else:
return "unloved-router"
def get_dns(self):
dns = []
names = "dns1 dns2"
for name in names:
if name in self.dbag['config']:
dns.append(self.dbag['config'][name])
return dns
def get_type(self):
if "type" in self.dbag['config']:
return self.dbag['config']['type']

View File

@ -19,12 +19,10 @@ import logging
from netaddr import *
from CsGuestNetwork import CsGuestNetwork
from cs.CsDatabag import CsDataBag
from cs.CsFile import CsFile
NO_PRELOAD = False
LEASES = "/var/lib/misc/dnsmasq.leases"
DHCP_HOSTS = "/etc/dhcphosts.txt"
DHCP_OPTS = "/etc/dhcpopts.txt"
DNSMASQ_CONF = "/etc/dnsmasq.conf"
CLOUD_CONF = "/etc/dnsmasq.d/cloud.conf"
@ -32,50 +30,68 @@ class CsDhcp(CsDataBag):
""" Manage dhcp entries """
def process(self):
dnsmasq = CsDnsMasq(self.config)
self.hosts = {}
self.changed = []
self.devinfo = CsHelper.get_device_info()
self.preseed()
self.cloud = CsFile(DHCP_HOSTS)
self.conf = CsFile(CLOUD_CONF)
length = len(self.conf)
for item in self.dbag:
if item == "id":
continue
dnsmasq.add(self.dbag[item])
dnsmasqb4 = CsDnsMasq(self.config, NO_PRELOAD)
dnsmasqb4.parse_hosts()
dnsmasqb4.parse_dnsmasq()
if not dnsmasq.compare_hosts(dnsmasqb4):
logging.info("Updating hosts file")
dnsmasq.write_hosts()
else:
logging.debug("Hosts file is up to date")
diff = dnsmasq.compare_dnsmasq(dnsmasqb4)
if len(diff) > 0:
dnsmasq.updated = True
dnsmasq.delete_leases(diff)
dnsmasq.write_dnsmasq()
dnsmasq.first_host = dnsmasqb4.first_host
dnsmasq.configure_server()
self.add(self.dbag[item])
self.write_hosts()
if self.cloud.is_changed():
self.delete_leases()
self.configure_server()
self.conf.commit()
self.cloud.commit()
if self.cloud.is_changed():
if length < 2:
CsHelper.service("dnsmasq", "restart")
else:
CsHelper.hup_dnsmasq("dnsmasq", "dnsmasq")
def configure_server(self):
#self.conf.addeq("dhcp-hostsfile=%s" % DHCP_HOSTS)
for i in self.devinfo:
if not i['dnsmasq']:
continue
device = i['dev']
ip = i['ip'].split('/')[0]
sline = "dhcp-range=interface:%s,set:interface" % (device)
line = "dhcp-range=interface:%s,set:interface-%s,%s,static" % (device, device, ip)
self.conf.search(sline, line)
gn = CsGuestNetwork(device, self.config)
sline = "dhcp-option=tag:interface-%s,15" % device
line = "dhcp-option=tag:interface-%s,15,%s" % (device, gn.get_domain())
self.conf.search(sline, line)
# DNS search order
sline = "dhcp-option=tag:interface-%s,119" % device
line = "dhcp-option=tag:interface-%s,119,%s" % (device, ','.join(gn.get_dns()))
self.conf.search(sline, line)
# Gateway
gateway = ''
if self.config.is_vpc():
gateway = gn.get_gateway()
else:
gateway = i['gateway']
sline = "dhcp-option=tag:interface-%s,3," % device
line = "dhcp-option=tag:interface-%s,3,%s" % (device, gateway)
self.conf.search(sline, line)
# Netmask
netmask = ''
if self.config.is_vpc():
netmask = gn.get_netmask()
else:
netmask = self.config.address().get_guest_netmask()
sline = "dhcp-option=tag:interface-%s,1," % device
line = "dhcp-option=tag:interface-%s,1,%s" % (device, netmask)
self.conf.search(sline, line)
class CsDnsMasq(object):
def __init__(self, config, preload=True):
self.list = []
self.hosts = []
self.leases = []
self.config = config
self.updated = False
self.devinfo = CsHelper.get_device_info()
self.devs = []
self.first_host = False
if preload:
self.add_host("127.0.0.1", "localhost")
self.add_host("::1", "localhost ip6-localhost ip6-loopback")
self.add_host("ff02::1", "ip6-allnodes")
self.add_host("ff02::2", "ip6-allrouters")
if config.is_vpc():
self.add_host("127.0.0.1", CsHelper.get_hostname())
if config.is_router():
self.add_host(self.config.address().get_guest_ip(), "%s data-server" % CsHelper.get_hostname())
def delete_leases(self, clist):
def delete_leases(self):
leases = []
try:
for line in open(LEASES):
bits = line.strip().split(' ')
@ -85,95 +101,55 @@ class CsDnsMasq(object):
"host": bits[3],
"del": False
}
for l in clist:
lbits = l.split(',')
if lbits[0] == to['mac'] or \
lbits[1] == to['ip']:
to['del'] is True
break
self.leases.append(to)
for o in self.leases:
for v in changed:
if v['mac'] == to['mac'] or v['ip'] == to['ip'] or v['host'] == to['host']:
to['del'] = True
leases.append(to)
for o in leases:
if o['del']:
cmd = "dhcp_release %s %s %s" % (o.device, o.ip, o.mac)
cmd = "dhcp_release eth%s %s %s" % (o.device, o.ip, o.mac)
logging.info(cmd)
CsHelper.execute(cmd)
# Finally add the new lease
except IOError:
return
def configure_server(self):
self.updated = self.updated | CsHelper.addifmissing(DNSMASQ_CONF, "dhcp-hostsfile=/etc/dhcphosts.txt")
# self.updated = self.updated | CsHelper.addifmissing(DNSMASQ_CONF, "dhcp-optsfile=%s:" % DHCP_OPTS)
for i in self.devinfo:
if not i['dnsmasq']:
continue
device = i['dev']
ip = i['ip'].split('/')[0]
line = "dhcp-range=interface:%s,set:interface-%s,%s,static" % (device, device, ip)
self.updated = self.updated | CsHelper.addifmissing(CLOUD_CONF, line)
# Next add the domain
# if this is a guest network get it there otherwise use the value in resolv.conf
gn = CsGuestNetwork(device, self.config)
line = "dhcp-option=tag:interface-%s,15,%s" % (device, gn.get_domain())
self.updated = self.updated | CsHelper.addifmissing(CLOUD_CONF, line)
if self.updated:
if self.first_host:
CsHelper.service("dnsmasq", "restart")
else:
CsHelper.hup_dnsmasq("dnsmasq", "dnsmasq")
def parse_dnsmasq(self):
self.first_host = False
try:
for line in open(DHCP_HOSTS):
self.list.append(line.strip())
if len(self.list) == 0:
self.first_host = True
except IOError:
self.first_host = True
def parse_hosts(self):
for line in open("/etc/hosts"):
line = line.rstrip().lstrip()
if line == '':
continue
if line.startswith("#"):
continue
bits = ' '.join(line.split()).split(' ', 1)
self.add_host(bits[0], bits[1])
def compare_hosts(self, obj):
return set(self.hosts) == set(obj.hosts)
def compare_dnsmasq(self, obj):
return list(set(self.list).symmetric_difference(set(obj.list)))
def preseed(self):
self.add_host("127.0.0.1", "localhost")
self.add_host("::1", "localhost ip6-localhost ip6-loopback")
self.add_host("ff02::1", "ip6-allnodes")
self.add_host("ff02::2", "ip6-allrouters")
if self.config.is_vpc():
self.add_host("127.0.0.1", CsHelper.get_hostname())
if self.config.is_router():
self.add_host(self.config.address().get_guest_ip(), "%s data-server" % CsHelper.get_hostname())
def write_hosts(self):
logging.debug("Updating hosts file")
handle = open("/etc/hosts", 'w+')
for line in self.hosts:
handle.write("%s\n" % line)
handle.close()
def write_dnsmasq(self):
logging.debug("Updating %s", DHCP_HOSTS)
handle = open(DHCP_HOSTS, 'w+')
for line in self.list:
handle.write("%s\n" % line)
b = line.split(',')
handle.close()
file = CsFile("/etc/hosts")
for ip in self.hosts:
file.search("%s" % ip, "%s\t%s" % (ip, self.hosts[ip]))
file.commit()
if file.is_changed():
logging.info("Updated hosts file")
else:
logging.debug("Hosts file unchanged")
def add(self, entry):
self.add_host(entry['ipv4_adress'], entry['host_name'])
self.add_dnsmasq(entry['ipv4_adress'], entry['host_name'], entry['mac_address'])
if self.cloud.search("%s," % entry['mac_address'],
"%s,%s,%s,infinite" % (entry['mac_address'],
entry['ipv4_adress'],
entry['host_name'])):
self.changed.append({'mac': entry['mac_address'],
'ip4': entry['ipv4_adress'],
'host': entry['host_name']})
i = IPAddress(entry['ipv4_adress'])
# Calculate the device
for v in self.devinfo:
if i > v['network'].network and i < v['network'].broadcast:
v['dnsmasq'] = True
# Virtual Router
v['gateway'] = entry['default_gateway']
def add_dnsmasq(self, ip, host, mac):
self.list.append("%s,%s,%s,infinite" % (mac, ip, host))
def add_host(self, ip, host):
self.hosts.append("%s\t%s" % (ip, host))
def add_host(self, ip, hosts):
self.hosts.update({ip: hosts})

View File

@ -46,6 +46,9 @@ class CsFile:
else:
return False
def __len__(self):
return len(self.config)
def empty(self):
self.config = []
self.new_config = []
@ -80,11 +83,12 @@ class CsFile:
def add(self, string, where=-1):
for index, line in enumerate(self.new_config):
if line.strip() == string:
return
return False
if where == -1:
self.new_config.append("%s\n" % string)
else:
self.new_config.insert(where, "%s\n" % string)
return True
def section(self, start, end, content):
sind = -1
@ -114,6 +118,8 @@ class CsFile:
self.new_config[index] = replace + "\n"
if not found:
self.new_config.append(replace + "\n")
return True
return False
def compare(self, o):
return (isinstance(o, self.__class__) and set(self.config) == set(o.new_config))