#!/usr/bin/python # # $Id: nodeboot.cgi.py 3695 2008-11-04 14:10:43Z ltsai $ # # Copyright 2007 Platform Computing Inc # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # # # This can be run by Apache, so be careful about paths! # NOTE: Do not use mod_python. See: # http://www.modpython.org/FAQ/faqw.py?req=show&file=faq02.013.htp # for why not. import os import sys import string import cgi import time sys.path.append("/opt/kusu/bin") sys.path.append("/opt/kusu/lib") import platform if platform.machine() == "x86_64": sys.path.append("/opt/kusu/lib64/python") sys.path.append("/opt/kusu/lib/python") sys.path.append("/opt/primitive/lib/python2.4/site-packages") from optparse import OptionParser from kusu.core.app import KusuApp from kusu.core.db import KusuDB from path import path class NodeInfo: """This class will provide the functions for getting the Node Installation Information, and for setting the database fields for a node """ def __init__(self, user='nobody',cgi=False): """__init__ - initializer for the class""" self.db = KusuDB() #in a CGI environment, we do not have access to the environment # so we try postgres first then mysql. This is less than ideal # but is the simplest fix until hot migration can be supported if cgi: try: self.db.connect('kusudb',user,driver='postgres') self.driver = 'postgres' except: self.db.connect('kusudb',user,driver='mysql') self.driver='mysql' # if it fails here, nodeboot.cgi will fail altogether else: self.db.connect('kusudb', user) # connect will handle the driver def getNIInfo(self, nodename, nodeip): """getNII - Generates the Node Installation Information for a given node.""" if not nodename: return # Create the nodeinfo line query = ('select repos.installers, repos.ostype, ' 'nodegroups.ngid, nodegroups.installtype, nodes.nid, nodegroups.type, repos.repoid from nodes, ' 'nodegroups, repos where nodes.ngid=nodegroups.ngid and ' 'nodegroups.repoid=repos.repoid and nodes.name="%s"' % nodename) try: self.db.execute(query) data = self.db.fetchone() except: # Return 500 sys.exit(-1) if not data: # Need to trigger a 500 error sys.exit(-1) installer, os, ngid, type, nid, ngtype, repoid = data repo = '/repos/' + str(repoid) #FIXME: WORK IN PROGRESS # Currently just use the ip from the master installer, where the # network request from the node is hitting. # We will use multiple installers next time when # we have other installers up and running. #query = ('select ip from nics,nodes where nics.nid=nodes.nid ' + # 'and nodes.name=(select kvalue from appglobals where kname="PrimaryInstaller") ' + # 'and netid = (select netid from nodes,nics where nodes.nid=nics.nid and nodes.name="%s" and nics.ip="%s")' % (nodename,nodeip)) query = ('SELECT ip FROM nics, nodes, networks WHERE nics.nid=nodes.nid AND nics.netid=networks.netid ' + 'AND nodes.name=(SELECT kvalue FROM appglobals WHERE kname=\'PrimaryInstaller\') ' + 'AND networks.network=(SELECT network FROM nodes, nics, networks WHERE nodes.nid=nics.nid ' + 'AND networks.netid=nics.netid AND nodes.name=\'%s\' AND nics.ip=\'%s\') ' % (nodename, nodeip) + 'AND networks.subnet=(SELECT subnet FROM nodes, nics, networks WHERE nodes.nid=nics.nid ' + 'AND networks.netid=nics.netid AND nodes.name=\'%s\' AND nics.ip=\'%s\')' % (nodename, nodeip)) try: self.db.execute(query) data = self.db.fetchone() except: # Return 500 sys.exit(-1) if not data: # Need to trigger a 500 error sys.exit(-1) installer = data[0] # This section could probably be removed etcdbpasswd = path('/opt/kusu/etc/db.passwd') dbpasswd = '' if not etcdbpasswd.exists(): print "Oops!" else: f = open(etcdbpasswd, 'r') dbpasswd = f.read().strip() f.close() cfmsecretfile = path('/etc/cfm/.cfmsecret') cfmsecret = '' if not cfmsecretfile.exists(): print "Oops!" else: f = open(cfmsecretfile, 'r') cfmsecret = f.read().strip() f.close() #Line 1 print '' % (nodename, installer, repo, os or '', type, ngid, ngtype, repoid, dbpasswd, cfmsecret) # NICinfo section query = ('select nics.ip, networks.usingdhcp, networks.network, ' 'networks.subnet, networks.device, networks.suffix, ' 'networks.gateway, networks.options, nics.boot, nics.mac, networks.type ' 'from nics,networks where networks.netid=nics.netid ' 'and nics.nid=%s' % nid) try: self.db.execute(query) data = self.db.fetchall() except: # Return 500 print "Oops! again" sys.exit(-1) if not data: # Need to trigger a 500 error print "Oops!" sys.exit(-1) for row in data: ip, dhcpb, network, subnet, dev, suffix, gw, opt, bootb, mac, ntype = row if dhcpb: dhcp = 1 else: dhcp = 0 if bootb: boot=1 else: boot=0 #Line 2 print ' ' % (dev, ip or '', subnet, network, suffix, gw, dhcp, opt or '', boot, mac or '', ntype) # Partition Info #default is 7 lines query = ('select device, partition, mntpnt, fstype, size, options, preserve ' 'from partitions where ngid=\'%i\'' % ngid ) try: self.db.execute(query) data = self.db.fetchall() except: # Return 500 print "Oops! again 3" sys.exit(-1) if data: for row in data: device, partition, mntpnt, fstype, size, options, preserveb = row if preserveb: # convert from boolean to digit. preserve = 1 else: preserve = 0 print ' ' % (device or '', partition or '', mntpnt or '', fstype or '', size, options or '', preserve) # Component Info try: if self.db.driver == 'mysql': query = ('select components.cname from components, kits, ng_has_comp ' 'where components.cid=ng_has_comp.cid and ' 'kits.kid=components.kid and kits.isOS=False and ' 'ng_has_comp.ngid=\'%i\'' % ngid ) self.db.execute(query) else: # postgres query = ('select components.cname from components, kits, ng_has_comp ' 'where components.cid=ng_has_comp.cid and ' 'kits.kid=components.kid and kits."isOS"=False and ' 'ng_has_comp.ngid=\'%i\'' % ngid ) self.db.execute(query,postgres_replace=False) data = self.db.fetchall() except: # Return 500 print "Oops! again 3" sys.exit(-1) if data: for row in data: cname = row[0] print ' %s' % cname # Optional packages query = ('select packagename from packages ' 'where ngid=\'%i\'' % ngid ) try: self.db.execute(query) data = self.db.fetchall() except: # Return 500 print "Oops! again 4" sys.exit(-1) if data: for row in data: pack = row[0] print ' %s' % pack # Optional scripts query = ('select script from scripts ' 'where ngid=\'%i\'' % ngid ) try: self.db.execute(query) data = self.db.fetchall() except: # Return 500 print "Oops! again 5" sys.exit(-1) if data: for row in data: script = row[0] print ' %s' % script # Appglobals query = ('select kname, kvalue from appglobals where ' 'ngid is NULL or ngid=\'%i\'' % ngid ) try: self.db.execute(query) data = self.db.fetchall() except: # Return 500 print "Oops! again 6" sys.exit(-1) if data: for row in data: kname, kvalue = row print ' ' % (kname, kvalue or '') print '' def getCFMfile(self): """getCFMfile - Displays the contents of the Cluster File Management (CFM) change file.""" cfmdir = self.db.getAppglobals('CFMBaseDir') if not cfmdir: return cfmfile = os.path.join(cfmdir, 'cfmfiles.lst') if os.path.exists(cfmfile): print '' print "CFMBaseDir=%s" % cfmdir try: fp = file(cfmfile, 'r') while True: line = fp.readline() if len(line) == 0: break sys.stdout.write(line) except: pass print '' fp.close() def getNodeName(self, nodename='', nodeip=''): """getNodeName - returns the name of the node given the IP, or name.""" if nodename: return nodename if not nodeip: return query = ('select nodes.name from nodes,nics where ' 'nodes.nid = nics.nid and nics.ip = \'%s\'' % nodeip) try: self.db.execute(query) except: # Return 500 sys.exit(-1) data = self.db.fetchone() if not data: # Need to trigger a 404 error, but don't see how. print "Cannot find host for IP: %s" % nodeip return '' return data[0] def setNodeInfo(self, nodename, state, bootfrom, timestamp): """setState - Set the nodes state in the database. One method is used to reduce the DB calls.""" if not state and bootfrom == -1 and not timestamp: return bf = False if bootfrom: bf = True cmdstr = 'update nodes set' if state: cmdstr = cmdstr + ' state=\'%s\',' % state if bootfrom != -1: cmdstr = cmdstr + ' bootfrom=%s,' % bf if timestamp: cmdstr = cmdstr + ' lastupdate=\'%s\',' % timestamp query = cmdstr[:-1] + ' where name=\'%s\'' % nodename try: self.db.execute(query) except: # Return 500 (hopefully) print "Query problem: %s" % query sys.exit(-1) def regenPXE(self, nodename): """regenPXE - Calls the boothost tool to cause the PXE file to be regenerated. NOTE: This forces the database connection to close! """ self.db.disconnect() if os.path.exists('/opt/kusu/sbin/boothost'): os.system('/opt/kusu/sbin/boothost -m %s' % nodename) def disconnect(self): self.db.disconnect() class NodeBootApp(KusuApp): def __init__(self): KusuApp.__init__(self) def toolVersion(self): """toolVersion - provide a version screen for this tool.""" self.errorMessage("Version %s\n", self.version) sys.exit(0) def parseargs(self): """parseargs - Parse the command line arguments and populate the class variables""" self.parser.add_option("-v", "--version", action="store_true", dest="getversion", default=False) self.parser.add_option("-d", "--displaynii", action="store_true", dest="getnii", default=False) self.parser.add_option("-f", "--displaycfm", action="store_true", dest="getcfm", default=False) self.parser.add_option("-i", "--nodeip", action="store", type="string", dest="nodeip") self.parser.add_option("-s", "--state", action="store", type="string", dest="nodestate") self.parser.add_option("-b", "--bootfrom", action="store", type="string", dest="bootfrom") (self.options, self.args) = self.parser.parse_args(sys.argv) def run(self): """run - Run the application""" self.parseargs() node = '' ip = '' dumpnii = 0 dumpcfm = 0 state = '' bootfrom = -1 # -1 means unchanged, 0=network, 1=disk updatepxe = 0 timestamp = '' # The timestamp to go in the nodes table runascgi = 0 # Flag to indicate this is running as a CGI # Test to see if we are running as a CGIless if os.environ.has_key('REMOTE_ADDR'): runascgi = 1 ip = os.environ['REMOTE_ADDR'] # Prepare the CGI class self.cgi = cgi.FieldStorage() if os.environ.has_key('REMOTE_HOST'): node = os.environ['REMOTE_HOST'] if self.cgi.has_key('dump'): if self.cgi['dump'].value[0] == '1': dumpnii = 1 if self.cgi.has_key('getindex'): if self.cgi['getindex'].value[0] == '1': dumpcfm = 1 if self.cgi.has_key('state'): if self.cgi['state'].value: state = self.cgi['state'].value[:19] if self.cgi.has_key('boot'): if self.cgi['boot'].value[0] == 'd': bootfrom = 1 else: bootfrom = 0 # Uncomment this for doing scalibility testing # Exercise with: wget 'http://HOSTNAME/repos/nodeboot.cgi?dump=1&ip=IP_ADDR&boot=' #if self.cgi.has_key('ip'): # if self.cgi['ip'].value: # ip = self.cgi['ip'].value[:] else: # Not running as a CGI if self.options.getversion: self.errorMessage("Version %s\n", self.version) sys.exit(0) else: # We need the nodes name for any of the following operations if self.options.nodeip: ip = self.options.nodeip else: self.errorMessage('nodeboot_no_hostname_or_ip\n') sys.exit(-1) if self.options.getnii: dumpnii = 1 if self.options.getcfm: dumpcfm = 1 if self.options.nodestate: state = self.options.nodestate[:19] if self.options.bootfrom: if self.options.bootfrom == 'disk': bootfrom = 1 else: bootfrom = 0 # The data should now be ready to call the methods if runascgi: print 'Content-type: text/html\n' #if self.cgi.has_key('dump'): print '' print "" print "" print "Dump NII: %s" % dumpnii print "State: %s" % state print "Dump CFM: %s" % dumpcfm # Test to see if we need write access to the database and connect if bootfrom or state: nodefun = NodeInfo('apache',cgi=runascgi) updatepxe = 1 else: nodefun = NodeInfo() if not node: node = nodefun.getNodeName('', ip) print "IP: %s" % ip print "Node: %s" % node print "" if not node: # Bail out print "" sys.exit(0) if dumpnii: nodefun.getNIInfo(node, ip) if dumpcfm: nodefun.getCFMfile() timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) print "" # Update State, bootfrom, and timestamp (Methods check validity) nodefun.setNodeInfo(node, state, bootfrom, timestamp) # Update PXE file if needed if updatepxe: nodefun.regenPXE(node) else: nodefun.disconnect() if __name__ == '__main__': app = NodeBootApp() app.run() sys.exit(0)