summaryrefslogtreecommitdiffstats
path: root/src/lib/Server/Plugins/Hostbase.py
blob: 9a488fc740ed0b737b514d16314b5513b54c4f0a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
'''This file provides the Hostbase plugin. It manages dns/dhcp/nis host information'''
__revision__ = '$Revision$'

from syslog import syslog, LOG_INFO
from lxml.etree import XML
from Cheetah.Template import Template
from Bcfg2.Server.Plugin import Plugin, PluginExecutionError, PluginInitError, DirectoryBacked

class DataNexus(DirectoryBacked):
    '''DataNexus is an object that watches multiple files and
    handles changes in an intelligent fashion.'''
    __name__ = 'DataNexus'

    def __init__(self, path, filelist, fam):
        self.files = filelist
        DirectoryBacked.__init__(self, path, fam)
        
    def HandleEvent(self, event):
        '''Trap adds and updates, else call baseclass HandleEvent'''
        action = event.code2str()
        if action in ['exists', 'created']:
            if (event.filename != self.name) and (event.filename not in self.files):
                syslog(LOG_INFO, "%s:Got event for unexpected file %s" % (self.__name__, event.filename))
                return
        DirectoryBacked.HandleEvent(self, event)
        if action != 'endExist' and event.filename != self.name:
            self.rebuildState(event)

    def rebuildState(self, event):
        '''This function is called when underlying data has changed'''
        pass

class Hostbase(Plugin, DataNexus):
    '''The Hostbase plugin handles host/network info'''
    __name__ = 'Hostbase'
    __version__ = '$Id$'
    __author__ = 'bcfg-dev@mcs.anl.gov'
    filepath = '/etc/bind'

    def __init__(self, core, datastore):
        self.ready = False
        files = ['dnsdata.xml', 'hostbase.xml', 'networks.xml']
        Plugin.__init__(self, core, datastore)
        try:
            DataNexus.__init__(self, datastore + '/Hostbase/data',
                               files, self.core.fam)
        except:
            self.LogError("Failed to load data directory")
            raise PluginInitError
        self.xdata = {}
        self.filedata = {}
        self.templates = {'zone':Template(open(self.data + '/templates/' + 'zonetemplate.tmpl').read()),
                          'reversesoa':Template(open(self.data + '/templates/' + 'reversesoa.tmpl').read()),
                          'named':Template(open(self.data + '/templates/' + 'namedtemplate.tmpl').read()),
                          'reverseapp':Template(open(self.data + '/templates/' + 'reverseappend.tmpl').read()),
                          'dhcp':Template(open(self.data + '/templates/' + 'dhcpd_template.tmpl').read())}
        self.Entries['ConfigFile'] = {}

    def FetchFile(self, entry, metadata):
        '''Return prebuilt file data'''
        fname = entry.get('name').split('/')[-1]
        if not self.filedata.has_key(fname):
            raise PluginExecutionError
        perms = {'owner':'root', 'group':'root', 'perms':'644'}
        [entry.attrib.__setitem__(key, value) for (key, value) in perms.iteritems()]
        entry.text = self.filedata[fname]

    def rebuildState(self, event):
        '''Pre-cache all state information for hostbase config files'''
        if self.entries.has_key(event.filename) and not self.xdata.has_key(event.filename):
            self.xdata[event.filename] = XML(self.entries[event.filename].data)
        if [item for item in self.files if not self.entries.has_key(item)]:
            return
        # we might be able to rebuild data more sparsely,
        # but hostbase.xml is the only one that will really change often
        # rebuild zoneinfo
        iplist = []
        for zone in self.xdata['dnsdata.xml']:
            zonehosts = []
            for host in [host for host in self.xdata['hostbase.xml']
                         if host.get('domain') == zone.get('domain')]:
                hostname = host.get('hostname')
                if zone.get('domain') == 'mcs.anl.gov':
                    ## special cases for the mcs.anl.gov domain
                    ## all machines have a "-eth" entry as well as an entry identifying their subnet
                    ## they also have their mail exchangers after every address
                    ipnodes = host.findall("interface/ip")
                    zonehosts.append((hostname, ipnodes[0].attrib['ip'], ipnodes[0].findall("name/mx"), None))
                    [zonehosts.append(("-".join([hostname, ipnode.attrib['dnssuffix']]), \
                                       ipnode.attrib['ip'], ipnode.findall("name/mx"), None))
                     for ipnode in ipnodes]
                    [zonehosts.append(("-".join([hostname, namenode.attrib['name']]), \
                                       ipnode.attrib['ip'], namenode.findall("mx"), None))
                     for ipnode in ipnodes
                     for namenode in ipnode
                     if namenode.attrib['name'] != ""]
                else:
                    ipnodes = host.findall("interface/ip")
                    zonehosts.append((host.attrib['hostname'], ipnodes[0].attrib['ip'], None, None))
                    [zonehosts.append(("-".join([host.attrib['hostname'], namenode.attrib['name']]),
                                       ipnode.attrib['ip'], None, None))
                     for ipnode in ipnodes
                     for namenode in ipnode
                     if namenode.attrib['name'] != ""]

                [zonehosts.append((host.attrib['hostname'], None, None, cnamenode.attrib['cname']))
                 for cnamenode in host.findall("interface/ip/name/cname")
                 if cnamenode.attrib['cname'] != ""]

                [iplist.append(ipnode.attrib['ip']) for ipnode in host.findall("interface/ip")]
            zonehosts.sort()
            self.templates['zone'].zone = zone
            self.templates['zone'].root = self.xdata['dnsdata.xml']
            self.templates['zone'].hosts = zonehosts
            self.filedata[zone.get('domain')] = str(self.templates['zone'])
        # now all zone forward files are built
        iplist.sort()
        filelist = []
        temp = None
        for x in range(len(iplist)-1):
            addressparts = iplist[x].split(".")
            if addressparts[:3] != iplist[x+1].split(".")[:3] and addressparts[:2] == iplist[x+1].split(".")[:2] \
            and ".".join([addressparts[1], addressparts[0]]) not in filelist:
                filelist.append(".".join([addressparts[1], addressparts[0]]))
            elif addressparts[:3] != iplist[x+1].split(".")[:3] and \
            addressparts[:2] != iplist[x+1].split(".")[:2] and \
            ".".join([addressparts[1], addressparts[0]]) not in filelist:
                filelist.append(".".join([addressparts[2], addressparts[1], addressparts[0]]))
            if x+1 == len(iplist) - 1:
                temp = iplist[x+1].split(".")
                if ".".join([temp[2], temp[1], temp[0]]) not in filelist \
                and ".".join([temp[1], temp[0]]) not in filelist:
                    filelist.append(".".join([temp[2], temp[1], temp[0]]))

        for filename in filelist:
            self.templates['reversesoa'].inaddr = filename
            self.templates['reversesoa'].zone = zone
            self.templates['reversesoa'].root = self.xdata['dnsdata.xml']
            self.filedata["%s.rev" % filename] = str(self.templates['reversesoa'])

        self.templates['named'].zones = self.xdata['dnsdata.xml']
        self.templates['named'].reverses = filelist
        self.filedata["named.conf"] = str(self.templates['named'])

        for filename in filelist:
            originlist = []
            towrite = filename.split(".")
            towrite.reverse()
            if len(towrite) > 2:
                self.templates['reverseapp'].hosts = [(ipnode.get('ip').split('.'), host.get('hostname'),
                                                       host.get('domain'), ipnode.get('num'), ipnode.get('dnssuffix'))
                                                      for host in self.xdata['hostbase.xml']
                                                      for ipnode in host.findall('interface/ip')
                                                      if ipnode.get('ip').split('.')[:3] == towrite]
                
                self.templates['reverseapp'].inaddr = filename
                self.templates['reverseapp'].fileorigin = None
                self.filedata["%s.rev" % filename] += str(self.templates['reverseapp'])
            else:
                revhosts = [(ipnode.get('ip').split('.'), host.get('hostname'), host.get('domain'),
                             ipnode.get('num'), ipnode.get('dnssuffix')) 
                            for host in self.xdata['hostbase.xml']
                            for ipnode in host.findall("interface/ip")
                            if ipnode.get('ip').split(".")[:2] == towrite]

                [originlist.append(".".join([reversehost[0][2], reversehost[0][1], reversehost[0][0]]))
                 for reversehost in revhosts
                 if ".".join([reversehost[0][2], reversehost[0][1], reversehost[0][0]]) not in originlist]

                revhosts.sort()
                originlist.sort()
                for origin in originlist:
                    outputlist = [rhost for rhost in revhosts
                     if ".".join([rhost[0][2], rhost[0][1], rhost[0][0]]) == origin] 
                    self.templates['reverseapp'].fileorigin = filename
                    self.templates['reverseapp'].hosts = outputlist
                    self.templates['reverseapp'].inaddr = origin
                    self.filedata["%s.rev" % filename] += str(self.templates['reverseapp'])
        self.buildDHCP()
        for key in self.filedata:
            self.Entries['ConfigFile']["%s/%s" % (self.filepath, key)] = self.FetchFile

    def buildDHCP(self):
        '''Pre-build dhcpd.conf and stash in the filedata table'''
        if 'networks.xml' not in self.xdata.keys():
            print "not running before networks is cached"
            return
        networkroot = self.xdata['networks.xml']
        if 'hostbase.xml' not in self.xdata.keys():
            print "not running before hostbase is cached"
            return
        hostbase = self.xdata['hostbase.xml']
        vlanandsublist = []
        subnets = networkroot.findall("subnet")
        for vlan in networkroot.findall("vlan"):
            vlansubs = vlan.findall("subnet")
            vlansubs.sort(lambda x, y: cmp(x.get("address"), y.get("address")))
            vlanandsublist.append((vlan, vlansubs))

        subnets140 = [subnet for subnet in subnets if subnet.attrib['address'].split(".")[0] == "140"]
        privatesubnets = [subnet for subnet in subnets if subnet.attrib['address'].split(".")[0] != "140"]
        subnets140.sort(lambda x, y: cmp(x.get("address"), y.get("address")))
        privatesubnets.sort(lambda x, y: cmp(x.get("address"), y.get("address")))

        dhcphosts = [host for host in hostbase if host.get('dhcp') == 'y' \
                     and host.find("interface").get('mac') != 'float' \
                     and host.find("interface").get('mac') != ""]

        hosts = []
        for host in dhcphosts:
            if len(host.findall("interface")) == 1 and len(host.findall("interface/ip")) == 1:
                hosts.append([host.get('hostname'), host.get('domain'), \
                              host.find("interface").get('mac'), \
                              host.find("interface/ip").get('ip')])
            elif len(host.findall("interface")) > 1:
                count = 0
                for interface in host.findall("interface"):
                    if count == 0 and interface.find("ip") is not None:
                        hostdata = [host.get('hostname'), host.get('domain'), \
                                    interface.get('mac'), interface.find("ip").get('ip')]
                    elif count != 0 and interface.find("ip") is not None:
                        hostdata = [host.get('hostname'), "-".join([host.get('domain'), str(count)]), \
                                    interface.get('mac'), interface.find("ip").get('ip')]
                    if len(interface.findall("ip")) > 1:
                        for ipnode in interface.findall("ip")[1:]:
                            hostdata[3] = ", ".join([hostdata[3], ipnode.get('ip')])
                    count += 1
                    hosts.append(hostdata)

        hosts.sort(lambda x, y: cmp(x[0], y[0]))
        self.templates['dhcp'].hosts = hosts
        self.templates['dhcp'].privatesubnets = privatesubnets
        self.templates['dhcp'].subnets140 = subnets140
        self.templates['dhcp'].vlans = vlanandsublist
        self.templates['dhcp'].networkroot = networkroot
        self.filedata['/etc/dhcpd.conf'] = str(self.templates['dhcp'])