f37 -> f39
[infrastructure.git] / nagios / configurator / NagiosConfig.py
1 import os
2 import sys
3 import xmlrpclib
4
5 ### downloaded makeHTML from:
6 ### article
7 ### http://www.hoboes.com/Mimsy/?ART=128
8 ### download \
9 ### http://www.hoboes.com/Mimsy/library/downloads/makeHTML.py.gz
10 import makeHTML
11
12 # utilities for building comon queries
13 import comon_query
14     
15
16 class NagiosConfig:
17
18     # static
19     _admin_role=10
20     _pi_role=20
21     # person emails to exclude
22     _exclude_admins={'maint@localhost.localdomain':True}
23
24     # set of services to check on each node
25     # these refer to generic services you must have in generic.cfg
26     _full_node_services = ['ssh',
27                            'ssh-root',
28                            'ssh-pl_conf',
29                            ]
30     _restricted_node_services = ['ssh',
31                                  'planetlab',
32                                  ]
33
34     _verbose = False
35 # WARNING : some site names come with wierd unicode chars
36 # in this case verbose mode might fail
37 #    _verbose = True
38
39     plchost=None
40     xmlrpcserver = None
41     auth=None
42
43     dest_email = None
44
45     # a tree of sites, for access to nodes and contacts
46     siteTree = None
47     # list of admin people
48     allAdmins = {}
49
50     ### record arguments
51     def __init__ (self, plchost, plcid, plcpasswd, plcrole):
52         self.plchost = plchost
53
54         self.auth={}
55         self.auth['Username'] = plcid
56         self.auth['AuthMethod'] = "password"
57         self.auth['AuthString'] = plcpasswd
58         self.auth['Role']=plcrole
59
60     def Auth (self):
61         return self.auth
62
63     ### connect to xml rpc server
64     def Connect (self):
65             
66         try:
67             self.xmlrpcserver = xmlrpclib.Server("https://%s/PLCAPI/"
68                                                  % (self.plchost))
69         except:
70             self.xmlrpcserver = None
71             
72         return self.xmlrpcserver
73
74     def set_verbose (self):
75         self._verbose =  True
76
77     def verbose (self,arg):
78         if (self._verbose):
79             print str(arg)
80
81     def GatherData (self, email, sitefile):
82
83         # if email is None, we NEED to have admin role
84         # otherwise let's dont wait 10 minutes before we fail
85         AdminRoleRequired = "admin role required, or provide -o/-O"
86         if not email:
87             if self.auth['Role'] != 'admin':
88                 raise AdminRoleRequired
89
90         self.dest_email = email
91
92         self.verbose ("Gathering")
93         server = self.Connect()
94         auth = self.Auth ()
95
96         if sitefile:
97             # open and parse the file, let's gather site_ids in the process
98             self.verbose("Opening site file %s"%sitefile)
99             siteF=open(sitefile,'r')
100             selected_ids=[]
101             while 1:
102                 siteline=siteF.readline()
103                 if not siteline:
104                     break
105                 selected_ids += [int(siteline.split(' ')[0])]
106             siteF.close()
107             sitesList = server.GetSites(auth,selected_ids)
108         else:
109             # get list of all sites
110             sitesList = server.GetSites(auth)
111         self.verbose("Working on a total of %d sites"%len(sitesList))
112
113         selectedNodeIds = [];
114         for site in sitesList:
115             selectedNodeIds += site['node_ids']
116
117         # get all nodenetworks
118         allNodeNetworks = server.GetNodeNetworks (auth)
119         allNodeNetworksIndex = dict ( [ ( nn['nodenetwork_id'],nn) for nn in allNodeNetworks ] )
120
121         # get list of nodes
122         # might wish to restrict the returned attributes
123         selectedNodes = server.GetNodes(auth,selectedNodeIds,['node_id','hostname','nodenetwork_ids'])
124         self.verbose("Got a total of %d nodes"%len(selectedNodes))
125
126         for node in selectedNodes:
127             if not node['nodenetwork_ids']:
128                 print 'WARNING : Node %s has no nodenetwork thus no IP'%node['hostname']
129             nn = allNodeNetworksIndex[node['nodenetwork_ids'][0]]
130             node['ip'] = nn['ip']
131         
132
133         # index it by node_id
134         selectedNodesIndex = dict( [ (node['node_id'],node) for node in selectedNodes ] )
135
136         self.siteTree = {}
137         for site in sitesList :
138             self.verbose( "SITE = %s" % site['name'])
139             site_id=site['site_id']
140             siteHash = {}
141             self.siteTree[site_id] = siteHash
142             node_ids = site['node_ids']
143             self.verbose( "NODES IDS = " + str(node_ids))
144             siteNodes=[ selectedNodesIndex[node_id] for node_id in node_ids ]
145             nodesHash = dict ( [ (node['node_id'],node) for node in siteNodes ] )
146             personsHash = {}
147             siteHash['site'] = site
148             siteHash['nodes'] = nodesHash
149             siteHash['persons'] = personsHash
150         
151         # We are provided a hard-wired e-mail :
152         # dont scan all people (maybe we cannot)
153         if not email:
154             
155             # get list of people
156             allPersons = server.GetPersons (auth, [],['person_id','email',
157                                                       'first_name','last_name'])
158             for person in allPersons:
159                 person_id = person['person_id']
160                 # keep only PIs
161                 roles = server.AdmGetPersonRoles (auth,person_id)
162                 is_admin = roles.has_key(str(self._admin_role))
163                 is_pi = roles.has_key(str(self._pi_role))
164                 if is_admin:
165                     ### handle exclude list
166                     if not self._exclude_admins.has_key(person['email']):
167                         self.allAdmins[person_id]=person
168                 elif is_pi:
169                     person_sites=server.AdmGetPersonSites(auth,person_id)
170                     for site_id in person_sites:
171                         personsHash = self.siteTree[site_id]['persons']
172                         personsHash[person_id]=person
173                         person['full_name']=person['first_name']+person['last_name']
174                         self.verbose("Added PI %s" % person['full_name'])
175
176     _define_host="""define host{
177         use                             generic-host
178         host_name                       %s
179         alias                           %s
180         address                         %s
181         contact_groups                  %s_pis
182         }\n"""
183     _define_hostgroup="""define hostgroup{
184         hostgroup_name                  %s
185         alias                           %s
186         members                         %s
187         }\n"""
188     # as of nagios-2.5-2.fc4 - apparently cannot use genericity in contacts
189     _define_contact="""define contact{
190         contact_name                    %s
191         alias                           %s %s
192         email                           %s
193         service_notification_period     24x7
194         host_notification_period        24x7
195         service_notification_options    w,u,c,r
196         host_notification_options       d,r
197         service_notification_commands   notify-by-email
198         host_notification_commands      host-notify-by-email
199         }\n"""
200     _define_contactgroup="""define contactgroup{
201         contactgroup_name               %s_pis
202         alias                           %s
203         members                         %s
204         }\n"""
205     _define_service="""define service{
206         use                             generic-%s
207         host_name                       %s
208         contact_groups                  %s_pis
209         servicegroups                   %s
210         }\n"""
211     _define_servicegroup="""define servicegroup{
212         servicegroup_name               %s
213         alias                           all %s
214         }\n"""
215
216     def WriteNagiosConfig (self, dirname):
217
218         site_ids=self.siteTree.keys()
219         site_ids.sort()
220
221
222         # by default use all avail services
223         node_services = self._full_node_services
224         # but in restricted mode some wont work out
225         if self.dest_email:
226             node_services = self._restricted_node_services
227
228         # generate contact file
229         # with people with admin roles, and also
230         # centraladmin if email was provided 
231         # located in main dir so we can have control on load order
232         filename = "%s/%s.cfg"%(dirname,"planetlab")
233         print ( "Opening %s for writing" % filename)
234         file = open (filename,'w')
235         for admin_id in self.allAdmins.keys():
236             admin=self.allAdmins[admin_id]
237             file.write(self._define_contact%
238                        ('rootadmin','root','admin',admin['email']))
239         if self.dest_email:
240             file.write(self._define_contact%
241                        (self.dest_email,'central','admin',self.dest_email))
242
243         for service in node_services:
244             file.write(self._define_servicegroup
245                        %(service,service))
246         
247         file.close()
248
249         ### manage planetlab subdir
250         planetlabdir="%s/planetlab/"%dirname
251         import os.path
252         if not os.path.isdir (planetlabdir):
253             try:
254                 print "Trying to create subdir %s"%planetlabdir
255                 os.mkdir (planetlabdir)
256             except Exception,e:
257                 print "Failed -- exiting"
258                 sys.exit(1)
259
260         import dircache
261         ls = dircache.listdir(planetlabdir)
262         import re
263         m=re.compile('^.*\.cfg$')
264         cfgs=filter(lambda x: m.match(x),ls)
265         if len(cfgs) != 0:
266             print "Warning : you have %d nagios configs left in %s"%(
267                 len(cfgs),planetlabdir)
268             print "It's OK to keep them, if that's what you're trying to do"
269
270         ### scan over sites
271         for site_id in site_ids:
272
273             siteHash=self.siteTree[site_id]
274             site = siteHash['site']
275             sitename = site['name']
276             sitebase = site['login_base']
277
278             nodes = siteHash['nodes']
279             node_ids = nodes.keys()
280             node_ids.sort()
281             if len(node_ids) != 0:
282
283                 filename = "%s/%s.cfg"%(planetlabdir,sitebase)
284                 print ( "Opening %s for writing" % filename)
285                 file = open (filename,'w')
286
287                 ### without email / use real people
288                 if not self.dest_email:
289                     persons = siteHash ['persons']
290
291                     self.verbose("retrieved PIs = %s"%str(persons))
292                         
293                     if len(persons) != 0:
294                         personnames=''
295                         for person_id in persons.keys():
296                             person=persons[person_id]
297                             personname=person['full_name']
298                             if len(personnames) == 0:
299                                 personnames = personname
300                             else:
301                                 personnames = "%s,%s"%(personnames,personname)
302                             file.write(self._define_contact
303                                        %(personname,
304                                          person['first_name'],
305                                          person['last_name'],
306                                          person['email']))
307
308                         file.write(self._define_contactgroup
309                                    %(sitebase,sitename,personnames))
310                 else:
311                     file.write(self._define_contactgroup
312                                %(sitebase,sitename,self.dest_email))
313                     
314
315                 nodenames=""
316                 for node_id in node_ids:
317                     node = nodes[node_id]
318                     if len(nodenames) == 0:
319                         nodenames = node['hostname']
320                     else:
321                         nodenames = "%s,%s"%(nodenames,node['hostname'])
322                     file.write(self._define_host
323                                %(node['hostname'],node['hostname'],
324                                  node['ip'],sitebase))
325
326                     for service in node_services:
327                         file.write(self._define_service
328                                    %(service,node['hostname'],sitebase,service))
329
330             
331                 file.write(self._define_hostgroup
332                            %(sitebase,sitename,nodenames))
333
334                 file.close()
335
336 #################### static html file with bookmarks to comon site-centric queries
337 # a first draft tried to create a column for easying the bookmarking process
338 # I did not completely succeed, because
339 # the javascript interface for firefox seems to only allow
340 # the creation of bookmarks in the sidebar
341 # in fact you can bookmark the link, but when you use that bookmark
342 # the target url is opened in the sidebar
343 # you can easily fix this by editing the bookmark and uncheck 
344 # a dialob box, but that's not what we want
345 # it's much easier to directly use the 'Bookmark this link'
346 # from the firefox menu
347 # so let's forget about this altogether
348
349 #    ### bookmarks
350 #    def ManualBookmark (self, url, text, bookmark_name):
351 #        bookmark_link="""<a href='javascript:addToFavorites("%s","%s")'>
352 #        <font color='#0000FF' face="Verdana">%s</font></a>"""
353 #        # the single quotes in the URL need to be html-encoded 
354 #        cleanurl = url.replace ("'","%27")
355 #        return bookmark_link%(makeHTML.encode(cleanurl),bookmark_name,text)
356 #
357 #    bookmark_code="""<script language="JavaScript" type="Text/Javascript">
358 #<!-- // Hide script from older browsers
359 #     // script by http://www.hypergurl.com
360 #     function addToFavorites(url,title) {
361 #       if (window.sidebar) { // Mozilla Firefox Bookmark
362 #          window.sidebar.addPanel(title, url,"");
363 #        } else if (window.external) { // IE Favorite
364 #           window.external.AddFavorite(url,title) 
365 #        } else {
366 #           alert("Sorry! Your browser doesn't support this function."); 
367 #        }
368 #     }
369 #// -->
370 #</script>""" ### ' for fooling emacs
371
372     def ComonNodesSelect (self,nodes):
373         names = map(lambda node: node['hostname'],nodes)
374         filters = map (comon_query.filter_node,names)
375         server = comon_query.SERVER
376         return comon_query.full_url(server,'||'.join(filters))
377
378     ### could not find the makeHTML tool for that
379     def ManualAnchor (self,name,value):
380         return '<a name="%s">%s</a>' % (name,value)
381
382     def SiteRow (self,site_id):
383     ### returns a (makeHTML) row for the given site
384
385         siteHash = self.siteTree[site_id]
386         site = siteHash['site']
387         sitename = site['name']
388         sitebase = site['login_base']
389
390         nodes = siteHash['nodes'].values()
391         if len(nodes) == 0 :
392             return None
393         else:
394             row = makeHTML.tableRow()
395             query=self.ComonNodesSelect(nodes)
396             row.addCells([makeHTML.link(content=sitename, url=query),
397                           self.ManualAnchor(sitebase,sitebase),
398 #                          self.ManualBookmark(query,
399 #                                              "Bookmark it",
400 #                                              "Comon on %s"%sitename),
401                           ])
402             return row
403
404     def MakeSiteTable (self, sites):
405         ### create table / sitename
406         table = makeHTML.table ()
407         ### first row
408         head_row = makeHTML.tableRow()
409         head_row.addCells ( ['Site name',
410                              'Site login',
411 #                             'Comon query',
412                              ])
413         table.addRow(rowList=[head_row])
414
415         for site in sites: 
416             table.addRow ( rowList=[self.SiteRow(site['site_id'])])
417         return table
418
419     ### Write a single index page that contains one bookmark per site
420     def WriteComonLinks (self, indexname):
421
422         pageHead = makeHTML.part('head')
423         pageHead.addPart('title',content='Comon links for all sites')
424 #        pageHead.addPiece(bookmark_code)
425         
426         pageBody = makeHTML.part('body')
427         pageBody.addPart('h1',content='Comon links, per site')
428
429         sites = map(lambda s: s['site'],self.siteTree.values())
430
431         pageBody.addPart('h2',content='By site name')
432
433         sites.sort (lambda s1,s2: cmp(s1['name'],s2['name']))
434         pageBody.addPart('p',self.MakeSiteTable(sites))
435
436         pageBody.addPart('h2',content='By site login')
437
438         sites.sort (lambda s1,s2: cmp(s1['login_base'],s2['login_base']))
439         pageBody.addPart('p',self.MakeSiteTable(sites))
440
441         fullPage = makeHTML.part('html')
442         fullPage.addPiece(pageHead)
443         fullPage.addPiece(pageBody)
444
445         text=fullPage.make()
446
447         print ( "Opening %s for writing" % indexname)
448         index=open (indexname,"w")
449         index.write(text+"\n")
450         index.close()