source: ccsd/trunk/crcnetd/modules/ccs_rrdbot.py @ 1284

Last change on this file since 1284 was 1284, checked in by ckb6, 6 years ago

The graphs server location is now automatic, rrdbot certs auto generated, now a per host service, smarter state locking for rrdbot-script, and general release work

  • Property svn:keywords set to Id
File size: 15.6 KB
Line 
1# Copyright (C) 2006  The University of Waikato
2#
3# This file is part of crcnetd - CRCnet Configuration System Daemon
4#
5# This handles the rrdbot service.
6#
7# Author:       Chris Browning <ckb6@cs.waikato.ac.nz>
8# Version:      $Id$
9#
10# crcnetd is free software; you can redistribute it and/or modify it under the
11# terms of the GNU General Public License version 2 as published by the Free
12# Software Foundation.
13#
14# crcnetd is distributed in the hope that it will be useful, but WITHOUT ANY
15# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17# details.
18#
19# You should have received a copy of the GNU General Public License along with
20# crcnetd; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22from crcnetd._utils.ccsd_common import *
23from crcnetd._utils.ccsd_log import *
24from crcnetd._utils.ccsd_events import *
25from crcnetd._utils.ccsd_session import getSession, getSessionE
26from crcnetd._utils.ccsd_server import exportViaXMLRPC
27from crcnetd._utils.ccsd_service import ccsd_service, ccs_service_error, \
28        registerService, getServiceInstance
29from crcnetd._utils.ccsd_cfengine import ccs_revision
30from crcnetd._utils.ccsd_ca import *
31import os.path
32import time
33import shutil
34
35ccs_mod_type = CCSD_SERVER
36
37class rrdbot_error(ccs_service_error):
38    pass
39   
40@exportViaXMLRPC(SESSION_RW, AUTH_USER)
41def snmp_found(session_id, hostname, classname, number, data=''):
42    '''Callback from rrdbot-script reporting a class instance'''
43
44    session = getSessionE(session_id)
45    sql = "SELECT a.host_id, b.class_id FROM host a , rrdbot_class b WHERE a.host_name=%s and b.class_name=%s"
46    res = session.query(sql, (hostname,classname)) 
47    host_id = res[0]["host_id"]
48    class_id = res[0]["class_id"]
49    link_id = 0;
50
51    #Attempt tp assciate the data to a link
52    if data != '':
53        sql = "SELECT link_id FROM interface WHERE ip_address=%s"
54        res = session.query(sql, (data))
55        if res:
56            link_id = res[0]["link_id"]
57
58    #Check to see if this class has been found before
59    sql = "SELECT num FROM snmp_discovery WHERE host_id=%s AND class_id=%s AND num=%s"
60    res = session.query(sql, (host_id, class_id, number))
61    #If the class exists update it else add it
62    if len(res) > 0:
63        sql = "UPDATE snmp_discovery SET ip_address=%s, link_id=%s, timestamp=current_timestamp \
64            WHERE host_id=%s AND class_id=%s AND num=%s"
65        session.execute(sql, (data, link_id, host_id, class_id, number))
66    else:   
67        sql = "INSERT INTO snmp_discovery VALUES(%s, %s, %s, %s, %s, current_timestamp)"       
68        session.execute(sql, (host_id, class_id, number, data, link_id))
69    return 1
70
71@catchEvent("interfaceAdded")
72def interfaceAdded(eventName, host_id, session_id, **params):
73    addSnmpInstruction(session_id, host_id, 3, None, None, None)
74
75@exportViaXMLRPC(SESSION_RW, AUTH_USER)
76def addSnmpInstruction(session_id, host_id, instr, param1=None, param2=None, param3=None):
77    '''Adds an instruction that rrdbot-script will deal with'''
78
79    session = getSessionE(session_id)
80    sql = "INSERT INTO snmp_instructions VALUES(%s, %s, %s, %s, %s)"       
81    session.execute(sql, (host_id, instr, param1, param2, param3))
82    return 1
83   
84@exportViaXMLRPC(SESSION_RW, AUTH_USER)
85def getSnmpInstructions(session_id):
86    '''Get all all snmp instructions'''
87    instrs = {1:"Probe for Timed out class", 2:"Probe for Timed out class instance", 3:"Re-probe host"}
88    session = getSessionE(session_id)
89    sql = "SELECT a.*, b.host_name FROM snmp_instructions a , host b WHERE b.host_id=a.host_id"
90    res = session.query(sql, ())
91    for r in res:
92        if r["instr"] in instrs.keys():
93            r["instr_name"] = instrs[r["instr"]]
94        else:
95            r["instr_name"] = "Unknowen"
96    return res
97   
98@exportViaXMLRPC(SESSION_RW, AUTH_USER)
99def setSnmpState(session_id,state):
100    '''Set the current state of rrdbot-script'''
101    lock = True
102    session = getSessionE(session_id)
103
104    #If wanting to lock see if its already locked
105    if state == 1:
106        sql = "SELECT state FROM snmp_state"
107        res = session.query(sql, ())
108        if res and res[0][0] != 0:
109            lock = False
110    if lock:
111        sql = "UPDATE snmp_state SET state=%s"
112        res = session.execute(sql, (state))
113        return 0
114    else:
115        return 1
116       
117@exportViaXMLRPC(SESSION_RO, AUTH_USER)
118def getSnmpState(session_id):
119    '''Get the current state of rrdbot-script'''
120
121    session = getSessionE(session_id)
122    sql = "SELECT state FROM snmp_state"
123    res = session.query(sql, ())
124    if res and res[0][0] == 0:
125        return round(300-(time.time()%300))
126    else:
127        return 0
128@exportViaXMLRPC(SESSION_RW, AUTH_USER)
129def flushSnmpInstructions(session_id):
130    '''Clear all snmp instructions'''
131
132    session = getSessionE(session_id)
133    sql = "DELETE FROM snmp_instructions"
134    session.execute(sql, ())
135    return 1   
136   
137@exportViaXMLRPC(SESSION_RW, AUTH_USER)
138def addClass(session_id, clas):
139    """Adds a new class to the database"""
140    session = getSessionE(session_id)
141   
142    # Build query
143    props = ["class_name", "poll", "interval", \
144            "cf", "archive", "depends"]
145    (sql, values) = buildInsertFromDict("rrdbot_class", props, clas)
146   
147    # Run query
148    session.execute(sql, values)
149
150    return 1
151   
152@exportViaXMLRPC(SESSION_RW, AUTH_USER)
153def addClassPart(session_id, part):
154    """Adds a new class part to the database"""
155    session = getSessionE(session_id)
156   
157    # Build query
158    props = ["class_id", "name", "poll", "type", \
159        "min", "max"]
160       
161    nulls= []
162
163    if "min" in part and part["min"] == '':
164        nulls.append("min")
165    if "max" in part and part["max"] == '':
166        nulls.append("max")
167
168    (sql, values) = buildInsertFromDict("rrdbot_class_parts", props, part, False, nulls)
169   
170    # Run query
171    session.execute(sql, values)
172
173    return 1
174   
175@exportViaXMLRPC(SESSION_RW, AUTH_ADMINISTRATOR)
176def removeClass(session_id, class_id):
177    """Removes class from the database."""
178    session = getSessionE(session_id)
179   
180    #First remove any dependants
181    sql = "SELECT * FROM rrdbot_class WHERE depends=%s"
182
183    res = session.query(sql, (class_id))
184    for clas in res:
185        sql = "UPDATE rrdbot_class SET depends=0, poll=\'%s%s\' WHERE class_id=%%s" % (clas["poll"], ":exist")
186        res2 = session.execute(sql, (clas["class_id"]))
187   
188    # Delete the class and its class parts
189    sql = "DELETE FROM rrdbot_class WHERE class_id=%s"
190    res = session.execute(sql, (class_id))
191    sql = "DELETE FROM rrdbot_class_parts WHERE class_id=%s"
192    res = session.execute(sql, (class_id))
193    return 1
194   
195@exportViaXMLRPC(SESSION_RW, AUTH_ADMINISTRATOR)
196def updateClassDetails(session_id, class_id, newDetails):
197    """Updates the details of the Class.
198
199    newDetails should be a dictionary containing only these class paramters
200    that have changed and need to be updated in the database.
201    """
202    session = getSessionE(session_id)
203       
204    # Build SQL
205    props = ["class_id", "class_name", "poll", "interval", \
206        "cf", "archive", "depends"]
207    (sql, values) = buildUpdateFromDict("rrdbot_class", props, newDetails, \
208            "class_id", class_id)
209       
210    if values == None:
211        # No changes made... ?
212        return 1
213   
214    # Run the query
215    session.execute(sql, values)
216   
217    return 1
218   
219@exportViaXMLRPC(SESSION_RW, AUTH_ADMINISTRATOR)
220def updateClassPartDetails(session_id, part_id, newDetails):
221    """Updates the details of the Class part.
222
223    newDetails should be a dictionary containing only these class paramters
224    that have changed and need to be updated in the database.
225    """
226    session = getSessionE(session_id)
227       
228    # Build SQL
229    props = ["part_id", "class_id", "name", "poll", "type", \
230        "min", "max"]
231    nulls= []
232
233    if "min" in newDetails and newDetails["min"] == '':
234        nulls.append("min")
235    if "max" in newDetails and newDetails["max"] == '':
236        nulls.append("max")
237
238    (sql, values) = buildUpdateFromDict("rrdbot_class_parts", props, newDetails, \
239            "part_id", part_id,False,nulls)
240       
241    if values == None:
242        # No changes made... ?
243        return 1
244   
245    # Run the query
246    session.execute(sql, values)
247
248    return 1 
249   
250@exportViaXMLRPC(SESSION_RO, AUTH_USER)
251def listClasses(session_id):
252    """Returns a list of classes"""
253    session = getSessionE(session_id)
254       
255    # Build SQL
256    sql = "SELECT * FROM rrdbot_class"   
257   
258    # Run the query
259    res = session.query(sql, ())
260    return res
261   
262@exportViaXMLRPC(SESSION_RO, AUTH_USER)
263def getClass(session_id,class_id):
264    """Returns a class"""
265    session = getSessionE(session_id)
266       
267    # Build SQL
268    sql = "SELECT * FROM rrdbot_class WHERE class_id=%s"   
269   
270    # Run the query
271    res = session.query(sql, (class_id))
272    return res
273   
274@exportViaXMLRPC(SESSION_RO, AUTH_USER)
275def getClassDepends(session_id,class_id):
276    """Returns a list of classes this class could be set to depend on"""
277    session = getSessionE(session_id)   
278    #If new class flag is set do a custom query
279    if class_id == -1:
280         # Build SQL
281        sql = "SELECT * FROM rrdbot_class WHERE depends=0"   
282   
283        # Run the query
284        res = session.query(sql, ())
285
286        return res
287
288    # Build SQL
289    sql = "SELECT * FROM rrdbot_class WHERE depends=%s"   
290   
291    # Run the query
292    res = session.query(sql, (class_id))
293
294    #If class is depended on it can depend on somthing else
295    if len(res) > 0:
296        return []
297
298    # Build SQL
299    sql = "SELECT * FROM rrdbot_class WHERE class_id!=%s AND depends=0"   
300   
301    # Run the query
302    res = session.query(sql, (class_id))
303
304    return res 
305   
306@exportViaXMLRPC(SESSION_RO, AUTH_USER)
307def getClassParts(session_id,class_id):
308    """Returns a list of Class parts"""
309    session = getSessionE(session_id)
310   
311    # Build SQL
312    sql = "SELECT * FROM rrdbot_class_parts WHERE class_id=%s"   
313   
314    # Run the query
315    res = session.query(sql, (class_id))
316    return res
317   
318@exportViaXMLRPC(SESSION_RO, AUTH_ADMINISTRATOR)
319def getClassPart(session_id, part_id):
320    """Returns a class part"""
321    session = getSessionE(session_id)
322   
323    # Build SQL
324    sql = "SELECT * FROM rrdbot_class_parts WHERE part_id=%s"   
325   
326    # Run the query
327    res = session.query(sql, (part_id))
328    return res
329
330@exportViaXMLRPC(SESSION_RW, AUTH_ADMINISTRATOR)
331def removeClassPart(session_id, part_id):
332    """Removes class part from the database."""
333    session = getSessionE(session_id)
334   
335    # Delete the class part
336    sql = "DELETE FROM rrdbot_class_parts WHERE part_id=%s"
337    res = session.execute(sql, (part_id))
338    return 1
339   
340
341@catchEvent("revisionPrepared")
342def handleRevisionPrepEvent(eventName, host_id, session_id, outputDir):
343    """Receives callbacks to add extra information to the config revisions"""
344   
345    # Get a list of hosts running the rrdbot service
346    rrdbot = getServiceInstance(session_id, ccs_rrdbot.serviceName)
347    hosts = rrdbot.getHostList()
348
349    ca = ccs_ca()
350
351    # Loop through each host and ensure that the certs/ directory is populated
352    for host in hosts:
353        try:
354            # Check basic path existance
355            hostdir = "%s/hosts/%s" % (outputDir, host)
356            if not os.path.isdir(hostdir):
357                # Host does not exist in the revision
358                continue
359            rrdbotdir = "%s/rrdbot" % (hostdir)
360            if not os.path.isdir(rrdbotdir):
361                log_warn("Host '%s' does not have rrdbot templates!" % host)
362                continue
363            # Now check for the certs directory and the certificates
364            certsdir = "%s/certs" % (rrdbotdir)
365            ensureDirExists(certsdir)
366            if not os.path.exists("%s/key.pem" % certsdir):
367                key = ca.getFile("ca/rrdbot-key.pem")
368                fp = open("%s/key.pem" % certsdir, "w")
369                fp.write(key)
370                fp.close()
371            if not os.path.exists("%s/cert.pem" % certsdir):
372                cert = ca.getFile("ca/rrdbot-cert.pem")
373                fp = open("%s/cert.pem" % certsdir, "w")
374                fp.write(cert)
375                fp.close()
376        except:
377            log_error("Could not setup rrdbot certificates for %s" % host, \
378                    sys.exc_info())
379   
380class ccs_rrdbot(ccsd_service):
381
382    serviceName = "rrdbot"
383    networkService = True
384
385    def __init__(self, session_id, service_id):
386        # Call base class setup
387        ccsd_service.__init__(self, session_id, service_id)
388
389    def getNetworkTemplateVariables(self):
390        """Returns a dictionary containing template variables.
391
392        WARNING: The returned dictionary could be very large!
393        """
394        variables = ccsd_service.getNetworkTemplateVariables(self)
395        session = getSessionE(ADMIN_SESSION_ID)
396        res = session.query("SELECT * FROM rrdbot_class",())
397        classes = {};
398        for clas in res:
399            clas["variables"] = session.query("SELECT * FROM \
400                rrdbot_class_parts WHERE class_id=%s",(clas["class_id"]))
401            if clas["depends"] != 0:
402                if clas["depends"] in classes:
403                    classes[clas["depends"]]["subs"].append(clas)
404                else:
405                    classes[clas["depends"]] = {}
406                    classes[clas["depends"]]["subs"] = []
407                    classes[clas["depends"]]["subs"].append(clas)
408            else:
409                classes[clas["class_id"]] = clas
410                classes[clas["class_id"]]["subs"] = []
411
412        variables['classes'] = classes
413
414        # Include a list of hosts that the service is enabled on
415        variables["hosts"] = self.getEnabledHostList()
416        variables["server"] = config_get_required("network", "server_name")
417        variables["port"] = config_get("ccsd", "port", DEFAULT_SERVER_PORT)
418        variables["graphport"] = config_get("graphs", "port", "80")
419        return variables
420
421           
422           
423    @staticmethod
424    def initialiseService():
425        """Called by the system the very first time the service is loaded.
426
427        This should setup an entry in the service table and load any default
428        service properties into the service_prop table.
429        """
430        session = getSessionE(ADMIN_SESSION_ID)
431        session.begin("initialising rrdbot service")       
432        try:
433           
434
435            session.execute("INSERT INTO service (service_id, service_name, " \
436                    "enabled) VALUES (DEFAULT, %s, DEFAULT)", \
437                    (ccs_rrdbot.serviceName))
438            service_id = session.getCountOf("SELECT currval('" \
439                    "service_service_id_seq') AS server_id", ())
440                   
441            # Read in schema and dump into database
442            fp = open("%s/ccs_graphs.schema" % \
443                    config_get("ccsd", "service_data_dir", \
444                    DEFAULT_SERVICE_DATA_DIR))
445            schema = fp.readlines()
446            fp.close()
447            session.execute("\n".join(schema), ())
448
449            # Commit the changeset
450            session.commit()
451           
452            log_info("Created rrdbot service entries and tables in database")
453        except:
454            session.rollback()
455            log_error("Unable to initialise rrdbot database entries!", \
456                    sys.exc_info())
457            raise rrdbot_error("Failed to setup database tables!")
458
459        return service_id
460
461       
462def ccs_init():
463    registerService(ccs_rrdbot)
464    #Ensure Certificates exist
465    ca = ccs_ca()
466    ca.ensureCertificateExists("rrdbot")
467    #Ensure state is set to 0
468    session = getSessionE(ADMIN_SESSION_ID)
469    sql = "UPDATE snmp_state SET state=0"
470    session.execute(sql, ())   
Note: See TracBrowser for help on using the repository browser.