#!/usr/bin/python -u # # Copyright (C) 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 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # # $Id: ngedit 3642 2008-10-28 10:05:22Z hsaliak $ # # Node Group Editor # Author: Alexey Tumanov (atumanov) import snack import sys import os import glob import string import re import subprocess import tempfile import shutil from sets import Set from kusu.ui.text.USXscreenfactory import USXBaseScreen,ScreenFactory from kusu.ui.text.USXnavigator import * from kusu.ui.text import USXkusuwidgets as kusuwidgets from kusu.core.app import KusuApp from kusu.core.db import KusuDB import kusu.util.log as kusulog from kusu.nodefun import validateNodeFormat import kusu.core.database as db from primitive.system.software.dispatcher import Dispatcher global curNG, origNG MAXWIDTH = 70 MAXHEIGHT = 18 LEFT=kusuwidgets.LEFT CENTER=kusuwidgets.CENTER RIGHT=kusuwidgets.RIGHT CFMBaseDir = "/etc/cfm" LockDir = "/var/lock/ngedit" def cfmCleanup(dbs, ngid): KusuCFMBaseDir = dbs.getAppglobals('CFMBaseDir') # Remove /opt/kusu/cfm/ directory ngcfmdir = os.path.join(KusuCFMBaseDir, '%s' % ngid) if os.path.isdir(ngcfmdir): shutil.rmtree(ngcfmdir) # Remove /opt/kusu/cfm/.package.lst as well packagelst = os.path.join(KusuCFMBaseDir, '%s.package.lst' % ngid) if os.path.exists(packagelst): os.unlink(packagelst) def copyPackagelst(dbs, src_ngid, dst_ngid): # Copy /opt/kusu/cfm/.packages.lst file as well KusuCFMBaseDir = dbs.getAppglobals('CFMBaseDir') dstNGpackagelst = os.path.join(KusuCFMBaseDir, '%s.package.lst' % dst_ngid) srcNGpackagelst = os.path.join(KusuCFMBaseDir, '%s.package.lst' % src_ngid) if not os.path.exists(dstNGpackagelst): # Using os.system here rather than shutil.copy2 because copy2 does # not preserve the ownership of the copied file. os.system('cp -a %s %s' % (srcNGpackagelst, dstNGpackagelst)) class scrNGEMain(USXBaseScreen): name= 'Node Group Editor' msg = 'Select the Node Group to operate on from the list below' buttons = ['edit_button', 'copy_button', 'delete_button', 'exit_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.setHelpLine('Select the Node Group to operate on and choose desired operation') self.__nglistbox = None def setCallbacks(self): #for the buttons self.buttonsDict['edit_button'].setCallback_(self.doEditAction) self.buttonsDict['copy_button'].setCallback_(self.doCopyAction) self.buttonsDict['delete_button'].setCallback_(self.doDeleteAction) #for hotkeys self.hotkeysDict['F12'] = self.F12Action def F12Action(self): return NAV_IGNORE def runTool(self, tool, argstr): kl.info("Final Actions: Running %s:" %tool) cmd = "%s %s" %(tool,argstr) p = subprocess.Popen( cmd, shell=True, stdout = subprocess.PIPE, stderr = subprocess.STDOUT ) prog_out = kusuwidgets.ProgressOutputWindow(self.selector.mainScreen, \ '%s progress' %tool, msg='',width=MAXWIDTH+3,height=MAXHEIGHT,scroll=0) output = '' t1 = time.time() while True: retval = p.poll() line = '' (rlist,wlist,xlist) = select.select([p.stdout.fileno()],[],[], 0.5) if rlist: line = p.stdout.readline() output += line prog_out.setText(output) prog_out.draw() prog_out.refresh() if not line and retval != None: break prog_out.close() t2 = time.time() retcode = p.returncode if not retcode: _title = "%s - success" %tool _msg = "%s reported successful return. Please inspect its output:\n" %tool else: _title = "%s - error" %tool _msg = tool + " returned an error, so its operation may be incomplete." +\ " Please refer to ngedit log file"+\ " for more detail. You can rerun the command manually as follows:\n"+\ cmd + "\nPlease inspect the output below:\n" _msg += output self.selector.popupMsg(_title, _msg, width = 60) kl.info("%s finished in %f sec. Output:\n%s" %(tool, (t2-t1),output)) def doCopyAction(self): ngid = self.__nglistbox.current() if ngid == 1: self.selector.popupMsg('Operation Disallowed', 'Installer nodegroup'+\ ' is not allowed to be copied') return NAV_NOTHING if ngid == 5: self.selector.popupMsg('Operation Disallowed', 'The Unmanaged nodegroup'+\ ' is not allowed to be copied') return NAV_NOTHING newNG = NodeGroup(ngid = ngid) try: newNG.syncFromDB(self.database) #to get all the links except: self.selector.popupMsg("Node Group Copying", "Cannot find the selected node group.") return NAV_NOTHING kl.debug("Copy Action performed. new NG record:\n%s" %str(newNG)) newNG['ngid'] = None #give it new identity query = "select ngname from nodegroups where ngname like '%s Copy %%'" %newNG['ngname'] self.database.execute(query) rv = self.database.fetchall() if not rv: suffix = 1 else: suffix = int(max(filter((lambda x: x.isdigit()), [string.split(x[0])[-1] for x in rv]))) + 1 ngname = '%s Copy %s' %(newNG['ngname'], suffix) old_ngname = newNG['ngname'] #save the old NG name newNG['ngname'] = ngname newNG.syncToDB(self.database) #assign new initrd imagename = 'initrd.%s.%s.img' %(newNG['installtype'], newNG.PKval) tmpNG = NodeGroup(ngid=newNG.PKval, initrd=imagename) tmpNG.syncToDB(self.database) #copy initrd, assuming it exists for original NG BootDir = self.database.getAppglobals('PIXIE_ROOT') if not BootDir: BootDir = '/tftpboot/kusu' if os.path.isfile(os.path.join(BootDir, newNG['initrd'])): shutil.copyfile(os.path.join(BootDir, newNG['initrd']), os.path.join(BootDir,imagename)) else: kl.info("WARNING: initrd '%s' does not exist for nodegroup '%s'" %(imagename,ngname)) # Copy the original image to the new image (Faster than rebuilding) oldImage = '%s.img.tar.bz2' % ngid newImage = '%s.img.tar.bz2' % newNG.PKval ImageDir = self.database.getAppglobals('DEPOT_IMAGES_ROOT') if not ImageDir: ImageDir = '/depot/images' if os.path.isfile(os.path.join(ImageDir, oldImage)): shutil.copyfile(os.path.join(ImageDir, oldImage), os.path.join(ImageDir, newImage)) # Apache must own it os.system('chown apache:apache \"%s\"' % os.path.join(ImageDir, newImage)) else: kl.info("WARNING: Image for '%s' does not exist!" % ngname) ### FIXME: Ice-integration kit should handle this for copied node groups. if 'ice' == self.database.getAppglobals('PROVISION').lower(): output = newNG.genKickstart(self, True) autoinst = self.database.getAppglobals('DEPOT_AUTOINST_ROOT') ngpath = os.path.join(autoinst, str(newNG['ngid'])) if not os.path.exists(ngpath) : os.makedirs(ngpath) # Convert ngname to an ICE acceptable version r = re.compile('[^\w.-]') nname = r.sub('_',ngname) ksfile = os.path.join(ngpath, '%s.cfg' % nname) ks = open(ksfile,'w') # overwrite ks.write(output) ks.close() # Generate the XML for HP-ICE icle_path = '/opt/repository/.icle' os.system("genconfig ICEintegration instconf '%s' > %s/%s.xml" % (ngname, icle_path, nname)) #copy CFM directory srcNGcfmdir = os.path.join(CFMBaseDir,old_ngname) dstNGcfmdir = os.path.join(CFMBaseDir,ngname) if os.path.isdir(srcNGcfmdir): if not os.path.exists(dstNGcfmdir): #copy the tree shutil.copytree(srcNGcfmdir,dstNGcfmdir,True) else: self.selector.popupMsg('CFM Directory Copy', "CFM Directory for nodegroup "+\ "'%s' already exists. It was left intact." % ngname) # Run the cfmsync so nodes will install properly self.runTool('cfmsync', "-n '%s' -f" % ngname) # self.selector.popupMsg('CFM Update', "Preparing CFM for node installation" ) copyPackagelst(dbs=self.database, src_ngid=ngid, dst_ngid=newNG.PKval) return NAV_NOTHING def doDeleteAction(self): ngid = self.__nglistbox.current() if 1 <= ngid and ngid <= 5: self.selector.popupMsg("Node Group Deletion", "Deletion of reserved node groups is not permitted.") return NAV_NOTHING tmpNG = NodeGroup(ngid = ngid) try: tmpNG.syncFromDB(self.database) #to get all the links except: self.selector.popupMsg("Node Group Deleting", "Cannot find the selected node group.") return NAV_NOTHING if isNGLockFileExists(ngid): self.selector.popupMsg("Node Group Deleting", "Cannot delete the node group '%s' because it " \ "is being modified. If it is not, then delete %s and try again." \ % (tmpNG['ngname'], getNGLockFile(ngid)) ) return NAV_NOTHING query = "select nid from nodes where ngid = %s" %ngid self.database.execute(query) rv = self.database.fetchall() if rv: self.selector.popupMsg("Node Group Deletion", "Unable to delete - it's in use. "+\ "Please (re)move the nodes from this NG first.") return NAV_NOTHING msg = "You are about to delete the selected nodegroup. Do you wish to proceed?" rv = self.selector.popupYesNo('Node Group Deletion Alert', msg, defaultNo=True) if rv: ngname = tmpNG['ngname'] tmpNG.eraseFromDB(self.database) #purge the CFM directory delNGcfmdir = os.path.join(CFMBaseDir, ngname) if os.path.isdir(delNGcfmdir): shutil.rmtree(delNGcfmdir) # Remove Images oldImage = '%s.img.tar.bz2' % ngid ImageDir = self.database.getAppglobals('DEPOT_IMAGES_ROOT') if not ImageDir: ImageDir = '/depot/images' if os.path.isfile(os.path.join(ImageDir, oldImage)): os.unlink(os.path.join(ImageDir, oldImage)) # Remove Initrd's oldInitrd = 'initrd.diskless.%s.img' % ngid BootDir = self.database.getAppglobals('PIXIE_ROOT') if not BootDir: BootDir = '/tftpboot/kusu' if os.path.isfile(os.path.join(BootDir, oldInitrd)): os.unlink(os.path.join(BootDir, oldInitrd)) oldInitrd = 'initrd.disked.%s.img' % ngid if os.path.isfile(os.path.join(BootDir, oldInitrd)): os.unlink(os.path.join(BootDir, oldInitrd)) ### FIXME: Ice-integration kit should manage integration related files. if 'ice' == self.database.getAppglobals('PROVISION').lower(): # Deleting the install configuration file autoinst = self.database.getAppglobals('DEPOT_AUTOINST_ROOT') ngpath = os.path.join(autoinst, str(ngid)) if os.path.isdir(ngpath): shutil.rmtree(ngpath) # Deleting the HP-ICE XML file for the nodegroup icle_path = '/opt/repository/.icle' r = re.compile('[^\w.-]') nname = r.sub('_',ngname) if os.path.isfile(os.path.join(icle_path, '%s.xml' % nname)): os.unlink(os.path.join(icle_path, '%s.xml' % nname)) cfmCleanup(self.database, ngid) return NAV_NOTHING def doEditAction(self, data=None): global curNG,origNG ngid = self.__nglistbox.current() if ngid == 5: # unmanaged self.selector.popupMsg("Node Group Editing", "Cannot currently modify the unmanaged nodegroup.") return NAV_NOTHING curNG = NodeGroup(ngid=ngid) try: curNG.syncFromDB(self.database) #to get all the links except: self.selector.popupMsg("Node Group Editing", "Cannot find the selected node group.") return NAV_NOTHING #replace all Null values for f in NodeGroup.fields[1:]: if curNG[f] == None: curNG[f] = '' if curNG['comps']: curNG['comps'] = [ int(x) for x in curNG['comps'] ] #work around Kusu bug 347 if curNG['parts']: for p in curNG['parts']: if p['options'] == None: p['options'] = '' if p['mntpnt'] == None: p['mntpnt'] = '' if p['device'] == None: p['device'] = '' origNG = curNG.copy() assert(curNG == origNG) # Lock the selected node group if isNGLockFileExists(curNG['ngid']): self.selector.popupMsg("Node Group Editing", "Cannot modify the node group '%s' because it " \ "is being modified. If it is not, then delete %s and try again." \ % (curNG['ngname'], getNGLockFile(curNG['ngid'])) ) return NAV_NOTHING createNGLockFile(curNG['ngid']) #pass on the db and kusuApp handles to other screens #they will all share one instance of each screenFactory = NGEScreenFactory([ scrNGEEdit_General(database=self.database, kusuApp=self.kusuApp), scrNGEEdit_Repo(database=self.database, kusuApp=self.kusuApp), scrNGEEdit_Components(database=self.database, kusuApp=self.kusuApp), scrNGEEdit_Networks(database=self.database, kusuApp=self.kusuApp), scrNGEEdit_Packages(database=self.database, kusuApp=self.kusuApp), scrNGEEdit_Scripts(database=self.database, kusuApp=self.kusuApp), scrNGEEdit_Finalize(database=self.database, kusuApp=self.kusuApp), scrNGEEdit_End(database=self.database, kusuApp=self.kusuApp) ]) provision = self.database.getAppglobals('PROVISION') if provision == 'KUSU': screenFactory.screens.insert(2, scrNGEEdit_Boot(database=self.database, kusuApp=self.kusuApp)) ks = USXNavigator(screenFactory, screenTitle="Node Group Edit", showTrail=False) ks.run() return NAV_NOTHING def drawImpl(self): cols = [ ['NODE GROUP', 'DESCRIPTION'] ] self.database.execute('''select max(char_length(ngname)), max(char_length(ngdesc)) from nodegroups''') rv = self.database.fetchone() rv = [ ifelse(x==None,0,x) for x in rv ] #replace all Null values cols.append([int(x) for x in rv]) #justification values headstr = '' for i in xrange(len(cols[0])): #for all columns cols[1][i] = max(cols[1][i], len(cols[0][i]))+1 #make room for the label headstr += cols[0][i].ljust(cols[1][i]) if len(headstr) > MAXWIDTH: headstr = headstr[:MAXWIDTH] lblheader = snack.Label(headstr) self.__nglistbox = snack.Listbox(height=10, scroll=1, width=MAXWIDTH,returnExit=1, showCursor=0) self.database.execute('select ngname,ngdesc,ngid from nodegroups') rv = self.database.fetchall() #Null value replacement magic rv = [ ifelse(None in x, [ ifelse(y==None,'',y) for y in x ] , list(x)) for x in rv ] for record in rv: ngid = record.pop() entrystr = '' for i in xrange(len(record)): #construct an entry string entrystr += record[i].ljust(cols[1][i]) if len(entrystr) > MAXWIDTH: entrystr = entrystr[:MAXWIDTH - len('...')] + '...' self.__nglistbox.append(entrystr, item = ngid) self.screenGrid = snack.Grid(1, 2) self.screenGrid.setField(lblheader, 0,0, padding=(0,0,0,0), growx=1,anchorLeft=1) self.screenGrid.setField(self.__nglistbox, 0,1, padding=(0,0,0,0), growx=1) #end class scrNGEMain class scrNGEEdit_General(USXBaseScreen): name = 'General Info' msg = 'General Info about the selected node' buttons = ['next_button', 'cancel_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) #for hotkeys self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doCancelAction def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def drawImpl(self): labeltxt1 = 'Node Group Name: ' labeltxt2 = 'Node Group Desc: ' labeltxt3 = 'Node Name Format: ' labellen = max(len(labeltxt1), len(labeltxt2), len(labeltxt3)) self.__entries = {} self.__entries['ngname'] = kusuwidgets.LabelledEntry(labelTxt=labeltxt1.rjust(labellen),\ text = curNG['ngname'], width=30, password=0, returnExit = 0) self.__entries['ngdesc'] = kusuwidgets.LabelledEntry(labelTxt=labeltxt2.rjust(labellen),\ text = curNG['ngdesc'], width=30, password=0, returnExit = 0) self.__entries['nameformat'] = kusuwidgets.LabelledEntry(labelTxt=labeltxt3.rjust(labellen),\ text = curNG['nameformat'], width=30, password=0, returnExit = 0) query = "select nid from nodes where ngid = %s" %curNG.PKval self.database.execute(query) rv = self.database.fetchall() if len(rv) >=1: self.__entries['nameformat'].setEnabled(False) self.screenGrid = snack.Grid(1, 3) self.screenGrid.setField(self.__entries['ngname'], col=0,row=0) self.screenGrid.setField(self.__entries['ngdesc'], col=0,row=1) self.screenGrid.setField(self.__entries['nameformat'], col=0,row=2) def validate(self): tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG['ngname'] = self.__entries['ngname'].value().strip() tmpNG['ngdesc'] = self.__entries['ngdesc'].value().strip() tmpNG['nameformat'] = self.__entries['nameformat'].value().strip() try: tmpNG.validateGeneralInfo(self.database) except NGEValidationError, e: return False, str(e) return True, 'Success' def formAction(self): #update curNG with entry values curNG['ngname'] = self.__entries['ngname'].value().strip() curNG['ngdesc'] = self.__entries['ngdesc'].value().strip() curNG['nameformat'] = self.__entries['nameformat'].value().strip() class scrNGEEdit_Repo(USXBaseScreen): name = 'Repository' msg = 'Repository selection for the Node Group specified' buttons = ['next_button', 'back_button', 'cancel_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) dbdriver = os.getenv('KUSU_DB_ENGINE', 'postgres') try: self.dbs = db.DB(dbdriver, 'kusudb', 'nobody') except UnsupportedDriverError, e: msg = 'Problems establishing database connection. Error: %s' % e self.logErrorEvent('Database error: ' + msg) sys.exit(1) def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['cancel_button'].setCallback_(self.doQuitAction) #for hotkeys self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doBackAction def doQuitAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def doBackAction(self): return NAV_BACK def drawImpl(self): cols = [ ['RepoName','Repository','OSType'] ] #extract all possible repos from table 'repos' and display self.database.execute('''select max(char_length(reponame)),max(char_length(repository)) , max(char_length(ostype)) from repos''') rv = self.database.fetchone() rv = [ ifelse(x==None,0,x) for x in rv ] cols.append([int(x) for x in rv]) #justification values headstr = '' for i in xrange(len(cols[0])): cols[1][i] = max(cols[1][i], len(cols[0][i])) + 1 headstr += cols[0][i].ljust(cols[1][i]) if len(headstr) > MAXWIDTH: headstr = headstr[:MAXWIDTH] lblheader = snack.Label(headstr) self.__repolistbox = snack.Listbox(height=10,scroll=1,width=MAXWIDTH,returnExit=1,showCursor=0) query = '''select r.reponame, r.repository, r.ostype, r.repoid from repos r, repos_have_kits rk, kits k where r.repoid = rk.repoid and rk.kid = k.kid ''' if self.database.driver == 'mysql': query += 'and k.isOS = True' self.database.execute(query) else: # Only postgres for now query += 'and k."isOS" = True' self.database.execute(query, postgres_replace=False) rv = self.database.fetchall() rv = [ ifelse(None in x, [ ifelse(y==None,'',y) for y in x ] , list(x)) for x in rv ] for record in rv: repoid = record.pop() entrystr = '' for i in xrange(len(record)): entrystr += record[i].ljust(cols[1][i]) if len(entrystr) > MAXWIDTH: entrystr = entrystr[:MAXWIDTH - len('...')] + '...' self.__repolistbox.append(entrystr, item = repoid) if repoid == curNG['repoid']: self.__repolistbox.setCurrent(repoid) self.screenGrid = snack.Grid(1, 2) self.screenGrid.setField(lblheader, 0,0, (0,0,0,0), growx=1, anchorLeft=1) self.screenGrid.setField(self.__repolistbox, 0,1, (0,0,0,0), growx=1) def validate(self): """Validation code goes here. Activated when 'Next' button is pressed.""" from kusu.repoman import tools as rtool repoid = self.__repolistbox.current() if rtool.isRepoStale(self.dbs, repoid): name = self.dbs.Repos.selectone_by(repoid=repoid).reponame return False, 'Repository is stale, please refresh using repoman -u -r %s' % name else: return True, 'Success' def formAction(self): ''' Actions taken once the form data was successfully validated. Timeline: Next_callback, validation, formAction ''' curNG['repoid'] = self.__repolistbox.current() # reset kernel and initrd to sane values if curNG['repoid'] <> origNG['repoid']: try: os = self.dbs.Repos.selectone_by(repoid=int(curNG['repoid'])).os except ValueError: msg = "Repository id '%s' could not be converted to integer" % curNG['repoid'] self.logErrorEvent('Validation error: ' + msg) os_tup = (os.name, os.major+'.'+os.minor, os.arch) if self.database.driver == 'mysql': query = '''select k.rname ,k.version, k.arch from kits k join repos_have_kits rk on k.kid = rk.kid where k.isOS = True and rk.repoid = %s ''' %curNG['repoid'] else: # Only postgres for now query = '''select k.rname ,k.version, k.arch from kits k join repos_have_kits rk on k.kid = rk.kid where k."isOS" = True and rk.repoid = %s ''' %curNG['repoid'] self.database.execute(query,postgres_replace=False) rv = self.database.fetchone() rname = rv[0] version =rv[1] arch = rv[2] curNG['kernel'] = "kernel-%s-%s.%s-%s" % (os.name , os.major, os.minor , os.arch) curNG['initrd'] = "initrd-%s-%s.%s-%s.img" % (os.name , os.major, os.minor, os.arch) curNG['kparams'] = Dispatcher.get('kparams', default='', os_tuple=os_tup) curNG['comps'] = [] if curNG['installtype'] == 'disked': curNG['packs'] = Dispatcher.get('imaged_packages', default=[], os_tuple=os_tup) curNG['modules'] = Dispatcher.get('imaged_modules', default=[], os_tuple=os_tup) elif curNG['installtype'] == 'diskless': curNG['packs'] = Dispatcher.get('diskless_packages', default=[], os_tuple=os_tup) curNG['modules'] = Dispatcher.get('diskless_modules', default=[], os_tuple=os_tup) else: # Handle case where user backs out repoID change. curNG['initrd'] = origNG['initrd'] curNG['kernel'] = origNG['kernel'] curNG['kparams'] = origNG['kparams'] curNG['packs'] = origNG['packs'] curNG['modules'] = origNG['modules'] class scrNGEEdit_Boot(USXBaseScreen): name = 'Boot Time Parameters' msg = 'Boot Time Parameters' buttons = ['next_button', 'back_button', 'cancel_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) BootDir = database.getAppglobals('PIXIE_ROOT') if not BootDir: BootDir = '/tftpboot/kusu' self.__kerneldir = BootDir def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) self.hotkeysDict['F12'] = self.F12Action #for hotkeys self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doBackAction def F12Action(self): self.selector.popupMsg('debug info','installtype = %s' %str(curNG['installtype'])) return NAV_IGNORE def doBackAction(self): return NAV_BACK def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def drawImpl(self): installtype = {'package':'Package Based', 'diskless':'Diskless', 'disked': 'Imaged Disked' } self.screenGrid = snack.Grid(2, 4) lbl1 = snack.Label('Kernel: ') lbl2 = snack.Label('Initrd: ') lbl3 = snack.Label('Kernel Params: ') lbl4 = snack.Label('Install Type: ') w = min(max(len(curNG['kernel']), len(curNG['kparams']), MAXWIDTH*1/2), MAXWIDTH) self.__kernellb = snack.Listbox(height=3, scroll=1,width=w,returnExit=1) self.__initrdlb = snack.Listbox(height=3, scroll=1,width=w,returnExit=1) kernels = glob.glob(os.path.join(self.__kerneldir,'kernel*')) kernels.extend( glob.glob(os.path.join(self.__kerneldir,'vmlinu*'))) for kernel in kernels: if os.path.isfile(kernel): self.__kernellb.append(os.path.basename(kernel),item=os.path.basename(kernel)) if curNG['kernel'] == kernel or curNG['kernel'] == os.path.basename(kernel): self.__kernellb.setCurrent(os.path.basename(kernel)) initrds = glob.glob(os.path.join(self.__kerneldir,'initrd*')) for initrd in initrds: if os.path.isfile(initrd): self.__initrdlb.append(os.path.basename(initrd),item=os.path.basename(initrd)) if curNG['initrd'] == initrd or curNG['initrd'] == os.path.basename(initrd): self.__initrdlb.setCurrent(os.path.basename(initrd)) self.__entries = {} self.__entries['kparams'] = snack.Entry(width=w, text=curNG['kparams']) # self.__entries['initrd'] = snack.Entry(width=w, text=curNG['initrd']) # self.__entries['initrd'].setFlags(snack.FLAG_DISABLED, snack.FLAGS_SET) rblist = [] for key,val in installtype.items(): isOn = int(curNG['installtype'] == key) #cast bool to int if isOn: rblist.append((val,key,isOn)) self.__InstallTypeRb = snack.RadioBar(self.screen, rblist) self.screenGrid.setField(lbl1,0,0,(0,0,0,1), anchorRight=1) self.screenGrid.setField(lbl2,0,1,anchorRight=1) self.screenGrid.setField(lbl3,0,2,anchorRight=1) self.screenGrid.setField(lbl4,0,3,anchorRight=1) self.screenGrid.setField(self.__kernellb, 1,0, (0,0,0,1), anchorLeft=1) self.screenGrid.setField(self.__initrdlb, 1,1,anchorLeft=1) self.screenGrid.setField(self.__entries['kparams'],1,2,anchorLeft=1) self.screenGrid.setField(self.__InstallTypeRb,1,3,anchorLeft=1) def validate(self): tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG['kernel'] = self.__kernellb.current() tmpNG['kparams'] = self.__entries['kparams'].value().strip() # Fixed tmpNG['initrd'] = self.__initrdlb.current() tmpNG['installtype'] = self.__InstallTypeRb.getSelection() tmpNG['type'] = curNG['type'] try: tmpNG.validateBootInfo(self.database) except NGEValidationError, e: return False, str(e) return True, 'Success' def formAction(self): curNG['kernel'] = self.__kernellb.current() curNG['initrd'] = self.__initrdlb.current() curNG['kparams'] = self.__entries['kparams'].value().strip() installtype = self.__InstallTypeRb.getSelection() if installtype <> curNG['installtype']: if curNG['initrd'] == self.__initrdlb.current(): #installtype has changed but initrd has not been changed. #change it for the user curNG['initrd'] = "initrd.%s.%s.img" %(installtype, curNG.PKval) curNG['installtype'] = installtype class scrNGEEdit_Components(USXBaseScreen): name = 'Components' msg = 'Please select components from the list below' buttons = ['next_button', 'back_button', 'cancel_button', 'toggleview_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) dbdriver = os.getenv('KUSU_DB_ENGINE', 'postgres') try: self.dbs = db.DB(dbdriver, 'kusudb', 'nobody') except UnsupportedDriverError, e: msg = 'Problems establishing database connection. Error: %s' % e self.logErrorEvent('Database error: ' + msg) sys.exit(1) self.__plugdir = '/opt/kusu/lib/plugins/ngedit' self.__ct = None self.filter_by_ngtype = True def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) self.buttonsDict['toggleview_button'].setCallback_(self.doToggleAction) self.hotkeysDict['F12'] = self.F12Action self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doBackAction def F12Action(self): return NAV_IGNORE def doBackAction(self): return NAV_BACK def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def doToggleAction(self): self.filter_by_ngtype = not self.filter_by_ngtype return NAV_NOTHING def drawImpl(self): self.screenGrid = snack.Grid(1, 2) repo = self.dbs.Repos.selectone_by(repoid=curNG['repoid']) ng_type = (self.filter_by_ngtype or None) and curNG['type'] # Either None or curNG['type'] comps = repo.getEligibleComponents(ng_type) # If filtering by ngtype, make sure to add all currently associated # components to the list of components to display. This prevents # associated components of a different ngtype from being dis-associated # unwittingly because of the ngtype filtering. if self.filter_by_ngtype: current_ng = self.dbs.NodeGroups.selectone_by(ngid=curNG['ngid']) all_components = repo.getEligibleComponents(ngtype=None) for comp in current_ng.components: if (comp not in comps) and (comp in all_components): comps.append(comp) rv = [(c.kid, c.kit.rname, c.cid, c.cname) for c in comps] rv.sort() if not rv: #no components to display self.msg = 'No components were found to match the selected repo' self.screenGrid.setField(snack.TextboxReflowed(text=self.msg, width=self.gridWidth),0,0) return if curNG.has_key('comps') and curNG['comps'] <> None: cidlst = curNG['comps'] else: if not origNG.has_key('comps') or origNG['comps'] == None: #first time - get the component list query = "select cid from ng_has_comp where ngid = %s" %curNG['ngid'] self.database.execute(query) tplrv = self.database.fetchall() origNG['comps'] = [x[0] for x in tplrv] cidlst = origNG['comps'] #ensure cidlst contains the OS component if self.database.driver == 'mysql': query = ''' select c.cid from repos_have_kits rk, kits k, components c where rk.kid = k.kid and k.kid = c.kid and k.isOS = True and rk.repoid = %s''' %curNG['repoid'] self.database.execute(query) else: # Only postgres for now query = ''' select c.cid from repos_have_kits rk, kits k, components c where rk.kid = k.kid and k.kid = c.kid and k."isOS" = True and rk.repoid = %s''' %curNG['repoid'] self.database.execute(query,postgres_replace=False) oscidtpl = self.database.fetchone() cidlst = list(Set(cidlst) | Set(oscidtpl)) self.__ct = snack.CheckboxTree(height = 10, scroll = 1) curkid = rv[0][0] self.__ct.append(rv[0][1]) i = 0 for kid,rname,cid,cname in rv: if kid <> curkid: #new kid encountered - append it i += 1 curkid = kid self.__ct.append(rname) if cid in cidlst: isSel = 1 else: isSel = 0 self.__ct.addItem(cname,(i,snack.snackArgs['append']), item=cid,selected=isSel) self.screenGrid.setField(snack.TextboxReflowed(text=self.msg,width=self.gridWidth), 0,0,growx=1,growy=1) self.screenGrid.setField(self.__ct,0,1,(0,1,0,0),growx=1,growy=1,anchorLeft=1) def validate(self): return True, 'Success' def formAction(self): # Check if component tree is defined; it will not be defined if this # repository has no components. if self.__ct: curNG['comps'] = [int(x) for x in self.__ct.getSelection()] #convert from long class scrNGEEdit_Networks(USXBaseScreen): name = 'Networks' msg = 'Please select the networks from the list below' buttons = ['next_button', 'back_button','restore_button', 'cancel_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.__devmap = {} def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['restore_button'].setCallback_(self.RestoreAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) self.hotkeysDict['F12'] = self.F12Action self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doBackAction def F12Action(self): return NAV_IGNORE def RestoreAction(self): curNG['nets'] = origNG['nets'][:] return NAV_NOTHING def doBackAction(self): return NAV_BACK def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def drawImpl(self): if curNG.has_key('nets') and curNG['nets'] <> None: netlst = curNG['nets'] else: if not origNG.has_key('nets') or origNG['nets'] == None: #first time - get the network list query = "select netid from ng_has_net where ngid = %s" %curNG['ngid'] self.database.execute(query) tplrv = self.database.fetchall() origNG['nets'] = [x[0] for x in tplrv] netlst = origNG['nets'] cols = [ ['DEVICE','NETWORK','SUBNET','DESCRIPTION'] ] self.database.execute('''select max(char_length(device)),max(char_length(network)) , max(char_length(subnet)), max(char_length(netname)) from networks''') rv = self.database.fetchone() cols.append([int(x) for x in rv]) #justification values headstr = ' ' #padding to align with checkboxtree items for i in xrange(len(cols[0])): #for all columns cols[1][i] = max(cols[1][i], len(cols[0][i]))+1 #make room for the label headstr += cols[0][i].ljust(cols[1][i]) if len(headstr) > MAXWIDTH: headstr = headstr[:MAXWIDTH] lblheader = snack.Label(headstr) self.__ct = snack.CheckboxTree(height = 10, scroll = 1) if self.database.driver =='mysql': query = "select device, IFNULL(network, 'DHCP'), IFNULL(subnet, 'DHCP'), IFNULL(netname, ''), netid from networks" else : # postgres only for now query = "select device, COALESCE(network,'DHCP'), COALESCE(subnet,'DHCP') , COALESCE(netname,''), netid from networks" self.database.execute(query) rv = self.database.fetchall() for record in rv: record = list(record) #convert to list netid = record.pop() self.__devmap[netid] = record[0] if netid in netlst: isSel = 1 else: isSel = 0 entrystr = '' for i in xrange(len(record)): #construct an entry string entrystr += record[i].ljust(cols[1][i]) if len(entrystr) > MAXWIDTH: entrystr = entrystr[:MAXWIDTH - len('...')] + '...' self.__ct.append(entrystr, item=netid, selected=isSel) #render self.screenGrid = snack.Grid(1, 2) self.screenGrid.setField(lblheader, 0,0, (0,0,0,0), anchorLeft=1) self.screenGrid.setField(self.__ct, 0,1, (0,0,0,0), anchorLeft=1) def validate(self): tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG['nets'] = [int(x) for x in self.__ct.getSelection()] #convert from long try: tmpNG.validateNetworks(self.database) except NGEValidationError, e: return False, str(e) return True, 'Success' def formAction(self): ''' Actions taken once the form data was successfully validated. Timeline: drawImpl, Next_callback, validation, formAction ''' curNG['nets'] = [int(x) for x in self.__ct.getSelection()] #convert from long class scrNGEEdit_Packages(USXBaseScreen): name = 'Optional Packages' msg = 'Please select optional packages to install' buttons = ['next_button', 'back_button', 'toggleview_button', 'cancel_button'] OldRepoId = None __ct_alpha = None #checkbox tree of alphabetized packages __ct_ctgry = None #checkbox tree of categorized packages def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.alphaview = True #alphabetical view by default, categorized view o.w. self.__ct = None #pointer to the current checkbox tree structure def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['toggleview_button'].setCallback_(self.TogglePackageView) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) self.hotkeysDict['F12'] = self.F12Action self.hotkeysDict['F10'] = self.TogglePackageView self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doBackAction def TogglePackageView(self): assert(self.__ct) self.alphaview = not self.alphaview curNG['packs'] = self.__ct.getSelection() time.sleep(0.5) #snack chokes if toggling too fast return NAV_NOTHING def F12Action(self): self.selector.popupMsg('DEBUG', str(self.__ct.getSelection())) return NAV_IGNORE def doBackAction(self): return NAV_BACK def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def drawImpl(self): self.screenGrid = snack.Grid(1, 1) timediff = [] #rebuild flag logic rebuild = curNG['repoid'] <> self.OldRepoId if rebuild: self.__ct_alpha = self.__ct_ctgry = self.OldRepoId = None #reset all else: if self.alphaview and self.__ct_alpha: self.__ct = self.__ct_alpha elif (not self.alphaview) and self.__ct_ctgry: self.__ct = self.__ct_ctgry else: rebuild = True if rebuild: self.__ct = snack.CheckboxTree(height = 13, width=MAXWIDTH, scroll = 1) if self.alphaview: #alphabetic view # pkgDict[letter] => package name pkgDict = getAvailPkgs(self.database, curNG['repoid'], categorized=False) i = -1 for letter in sorted(pkgDict.keys()): self.__ct.append(letter) i += 1 for p in sorted(pkgDict[letter]): self.__ct.addItem(p,(i,snack.snackArgs['append']), item=p) self.__ct_alpha = self.__ct self.OldRepoId = curNG['repoid'] else: #category view prog_dlg = self.selector.popupProgress('Categorized view', 'Generating categorized view ...') # pkgDict[category][group] => [package,package,...] t1 = time.time() pkgDict = getAvailPkgs(self.database, curNG['repoid'], categorized=True) ci = 0 #category index for c in sorted(pkgDict.keys()): self.__ct.append(c) # OLD: self.__ct.append(c.name, item=c.categoryid) gi = 0 #group index for g in sorted(pkgDict[c]): self.__ct.addItem(g, path=(ci,-1)) # OLD: self.__ct.addItem(g, path=(ci,-1), item=ginst.groupid) for p in sorted(pkgDict[c][g]): self.__ct.addItem(p, path=(ci,gi,-1), item=p) gi +=1 #done group ci += 1 #done category t2 = time.time() prog_dlg.close() self.__ct_ctgry = self.__ct self.OldRepoId = curNG['repoid'] #mark currently selected packages s1 = Set(self.__ct.getSelection()) curpacklst = [] #require selection if curNG['packs']: curpacklst = curNG['packs'] s2 = Set(curpacklst) for p in s1-s2: self.__ct.setEntryValue(item=p, selected=0) #note: the user may have changed the repo, hence some packages in #curNG['packs'] (a.k.a s2) may not be in the currently selected repo for p in s2-s1: try: self.__ct.setEntryValue(item=p, selected=1) except KeyError,msg: continue #at this point self.__ct is ready to be displayed self.screenGrid.setField(self.__ct,0,0,(0,0,0,0),growx=1,growy=1,anchorLeft=1) def validate(self): return True, 'Success' def formAction(self): curNG['packs'] = self.__ct.getSelection() bIsPackaged = curNG['installtype'] == 'package' bModNext = isinstance(self.selector.screens[self.selector.currentStep+1], scrNGEEdit_Modules) if bIsPackaged ^ bModNext: return if bIsPackaged and bModNext: #remove module screen from the sequence del self.selector.screens[self.selector.currentStep+1] else: #add module screen to the sequence self.selector.screens.insert(self.selector.currentStep+1,\ scrNGEEdit_Modules(database=self.database, kusuApp=self.kusuApp)) class scrNGEEdit_Modules(USXBaseScreen): name = 'Modules' buttons = ['next_button', 'back_button', 'cancel_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) self.hotkeysDict['F12'] = self.F12Action self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doBackAction def F12Action(self): self.selector.popupMsg('DEBUG', str(self.__ct.getSelection())) return NAV_IGNORE def doBackAction(self): return NAV_BACK def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def drawImpl(self): self.screenGrid = snack.Grid(1, 1) self.__ct = snack.CheckboxTree(height = 13, width=MAXWIDTH, scroll = 1) if not curNG['comps']: #no components chosen for this nodegroup msg = 'No components associated with this nodegroup. No modules to display.' self.screenGrid.setField(snack.TextboxReflowed(text=msg,width=self.gridWidth), 0,0,growx=1,growy=1) return assert(len(curNG['comps'])) prog_dlg = self.selector.popupProgress('Driver Package Extraction', 'Generating module list...') modDict = getAvailModules(self.database, curNG['ngid'], curNG['repoid'], curNG['comps']) prog_dlg.close() i = -1 for letter in sorted(modDict.keys()): maxlen = max(map(len, [m['name'] for m in modDict[letter]])) self.__ct.append(letter) i += 1 for m in sorted(modDict[letter]): modName = m['name'] modDesc = m['desc'] str2add = (modName.ljust(maxlen+1) + modDesc)[:MAXWIDTH] isSel = 0 if modName in curNG['modules']: isSel = 1 self.__ct.addItem(str2add,(i,snack.snackArgs['append']), item=modName, selected=isSel) self.screenGrid.setField(self.__ct,0,0,(0,0,0,0),growx=1,growy=1,anchorLeft=1) def validate(self): return True, 'Success' def formAction(self): curNG['modules'] = [x for x in curNG['modules'] if x in self.__ct.getSelection()] kl.debug("MODULES Selected: %s" %str(curNG['modules'])) #self.selector.popupMsg('selected modules', str(curNG['modules'])) class scrNGEEdit_PartSchema(USXBaseScreen): name = 'Partition Schema' buttons = [ 'next_button', 'back_button', 'new_button','edit_button', 'delete_button', 'cancel_button' ] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.PartSchema = None def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) self.buttonsDict['new_button'].setCallback_(self.doNewAction) self.buttonsDict['edit_button'].setCallback_(self.doEditAction) self.buttonsDict['delete_button'].setCallback_(self.doDeleteAction) self.hotkeysDict['F12'] = self.F12Action self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doBackAction def F12Action(self): id = self.listbox.current() if id != None: partrec = self.PartSchema.getPartRecByPK(id) dct = self.PartSchema.pk2dict[id] self.selector.popupMsg('DEBUG', 'currently pointing to id = %s\npartrec=%s\ndict=%s'\ %(id,str(partrec),str(dct))) return NAV_IGNORE def doNewAction(self): # populate list of items to pick optionList = [('Partition', 'partition')] provision = self.database.getAppglobals('PROVISION') if provision == 'KUSU': pv_dict = self.PartSchema.getPVMap() #getpvmap returns a pv key and vg value. the value is none if # the PV is unassociated, in which case we can add a new VG # To add new LVs we check that there is at least 1 pv that is # associated with a VG. if pv_dict and [ vg for vg in pv_dict.values() if vg]: optionList.append(('Logical Volume','log_vol')) if pv_dict and [ vg for vg in pv_dict.values() if not vg]: optionList.append(('Volume Group', 'vol_grp')) button,item = self.selector.popupListBox( 'Select type of device', "Please select what you'd like to create", optionList ) if button != 'cancel': screenList = [] if item == 'partition': screenList = [ scrPartSchema_PartEdit(self.PartSchema,id=None,database=self.database, kusuApp=self.kusuApp) ] elif item == 'log_vol': screenList = [ scrPartSchema_LVEdit(self.PartSchema,id=None,database=self.database, kusuApp=self.kusuApp) ] elif item == "vol_grp": screenList = [ scrPartSchema_VGEdit(self.PartSchema,id=None,database=self.database, kusuApp=self.kusuApp) ] if screenList: screenFactory = NGEScreenFactory(screenList) ks = USXNavigator(screenFactory, screenTitle="Partition Schema Modification", showTrail=False) ks.run() return NAV_NOTHING def doEditAction(self): id = self.listbox.current() screenList = [] if self.PartSchema.isPartition(id): screenList = [ scrPartSchema_PartEdit(self.PartSchema,id=id,database=self.database, kusuApp=self.kusuApp)] elif self.PartSchema.isLV(id): screenList = [ scrPartSchema_LVEdit(self.PartSchema,id=id,database=self.database, kusuApp=self.kusuApp)] elif self.PartSchema.isVG(id): screenList = [ scrPartSchema_VGEdit(self.PartSchema,id=id,database=self.database, kusuApp=self.kusuApp)] else: self.selector.popupMsg('Error', 'Modification allowed for partitions, VGs, & LVs only') if screenList: screenFactory = NGEScreenFactory(screenList) ks = USXNavigator(screenFactory, screenTitle="Partition Schema Modification", showTrail=False) ks.run() return NAV_NOTHING def doDeleteAction(self): id = self.listbox.current() if not id: self.selector.popupMsg('Delete Operation', 'Currently selected item is not deletable') return NAV_NOTHING try: tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG.setPartitionSchema(self.PartSchema) tmpNG.removePartition(self.database, id) except NGEPartRemoveError, e: self.selector.popupMsg("Delete Operation", str(e)) return NAV_NOTHING return NAV_NOTHING def doBackAction(self): return NAV_BACK def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def drawImpl(self): provision = self.database.getAppglobals('PROVISION') if not self.PartSchema: self.PartSchema = PartSchema() #triggers disk_profile creation (slow!) schema = self.PartSchema.mycreateSchema(curNG['parts']) else: schema = self.PartSchema.mycreateSchema() self.screenGrid = snack.Grid(1, 1) if provision == 'KUSU': self.listbox = kusuwidgets.ColumnListbox(height=8, colWidths=[15,15,15,15], colLabels=['Device ', 'Size(MB) ', 'Type ', 'Mount Point '], justification=[LEFT,RIGHT, LEFT, LEFT]) lvg_keys = schema['vg_dict'].keys() for key in sorted(lvg_keys): lvg = schema['vg_dict'][key] lvg_displayname = 'VG ' + key lvg_size_MB = '' # display volume groups first in listbox self.listbox.addRow(['VG ' + key, str(lvg_size_MB), 'VolGroup', ''], objRef=lvg['instid']) lv_keys = lvg['lv_dict'].keys() for lv_key in sorted(lv_keys): lv = lvg['lv_dict'][lv_key] lv_devicename = ' LV ' + lv_key lv_size_MB = lv['size_MB'] # display indented logical volumes belonging to the vg. self.listbox.addRow([lv_devicename, str(lv_size_MB),lv['fs'], lv['mountpoint']], objRef=lv['instid']) else: # add kickstart gen screen to sequence # self.selector.screens.insert(self.selector.currentStep+1,\ # scrNGEEdit_GenKickstart(database=self.database, kusuApp=self.kusuApp)) self.listbox = kusuwidgets.ColumnListbox(height=8, colWidths=[15,15,15], colLabels=['Size(MB) ', 'Type ', 'Mount Point '], justification=[LEFT, LEFT, LEFT], returnExit=0) disk_keys = schema['disk_dict'].keys() for key in sorted(disk_keys): # display device device = schema['disk_dict'][key] if provision == 'KUSU': self.listbox.addRow(['Disk '+str(key), '', '', ''] ) parts_dict = device['partition_dict'] parts_keys = parts_dict.keys() for part_key in sorted(parts_keys): partition = parts_dict[part_key] part_devicename = ' ' +'d'+ str(key) +'p'+ str(part_key) # indent one more level if logical partition. #if partition.part_type == 'logical': part_devicename = ' ' + part_devicename fs_type = partition['fs'] mountpoint = partition['mountpoint'] if fs_type == 'physical volume': partrec = self.PartSchema.getPartRecByPK(partition['instid']) (ispv,vg) = translatePartitionOptions(partrec['options'],'pv') if ispv and vg: mountpoint = vg # display partition info if provision == 'KUSU': self.listbox.addRow([part_devicename, str(partition['size_MB']), fs_type, #mountpoint], partition['instid']) mountpoint], partition['instid']) else: self.listbox.addRow([ str(partition['size_MB']), fs_type, #mountpoint], partition['instid']) mountpoint], partition['instid']) if self.listbox.length < 1: msg = 'no partition schema associated with the current Node Group' self.screenGrid.setField(snack.TextboxReflowed(width=MAXWIDTH, maxHeight=10, text=msg), 0,0, (0,0,0,0), anchorLeft=1) else: self.screenGrid.setField(self.listbox, col=0, row=0, anchorLeft=1, padding=(0,0,0,0)) def validate(self): # collate a list of mountpoints and ensure that they are unique part_recs = self.PartSchema.PartRecList mountpoint_list = [] duplicates = [] mountpoint_list.extend([ p['mntpnt'] for p in part_recs if p['mntpnt'] ]) for v in mountpoint_list: # o(n^2) fix not good if v not in duplicates: if mountpoint_list.count(v) > 1: duplicates.append(v) if duplicates: if len(duplicates) == 1: msg = "Mount point %s is defined more than once.Mount points have to\ be unique" % duplicates[0] else: msg = "Mount points %s are defined more than once.Mount points have\ to be unique" % str(duplicates) return False, msg return True, 'Success' def formAction(self): curNG['parts'] = self.PartSchema.PartRecList #self.selector.popupMsg('partition schema', "%s\n%s" %(str(curNG['parts']), str(curNG == origNG))) #self.selector.popupMsg('original part schema', 'origNG[parts]= %s' %str(origNG['parts'])) class _screenValidationHelper: ''' This class is a helper class for the partition and LV edit screens to validate editable fields. Each validation function only performs one logical action. KusuWidgets allow registering more than 1 callback. Multiple callbacks should be chosen. This class is still very specific to LV and Partition edit screens, but can be extended as per requirements later. ''' def __init__(self,unmountable_partitions=[]): ''' init data relevant to the validation ''' self.unmountable_partitions = unmountable_partitions # the calling class is responsible for updating this self.current_filesystem = '' self.NO_MNTPT = '' def mountPointCheck(self,mount): ''' prototype of function defined by LabelledEntry::verify() of kusuwidgets.py. Do not change. uses current_filesystem which the parent class is required to keep updated via a callback ''' if self.current_filesystem in self.unmountable_partitions and\ mount != self.NO_MNTPT: return False,'An unmountable partition has a partition entry recorded\ please file a bug report.This can be worked around by clearing the entry' else: #validate on mount point if not mount: return False,"Please define a mount point for the partition." elif len(mount) < 1 or not mount.startswith('/') : return False,'Absolute paths must be specified for mount points.\ They must begin with a "/"' return True,None def isAPositiveNumberCheck(self,input): ''' this checks if an input is a number. Used on entries that take a size input. LabelledEntry::verify() itself does a check for empty input ''' if not input: return False,'Please enter a value' try: if int(input) < 0: return False, 'Please enter value greater than 0' except ValueError: #the msg string has to be concatentated appropriately return False,'Please enter a valid number as the input' except TypeError: #This should not happen, indicates a bug return False,'Please file a bug report. A wrong type has been input' return True,None class scrPartSchema_PartEdit(USXBaseScreen): ''' This screen class allows to edit existing or create new Partitions. It exposes PartSchemaObj to the user for modification. ''' name = "Partition Modification" buttons = [ 'ok_button', 'cancel_button' ] def __init__(self, PartSchemaObj, id=None, database=None, kusuApp=None,gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.PartSchemaObj = PartSchemaObj #passed by ref. self.partition = PartSchemaObj.getDictByPK(id) self.PartRec = PartSchemaObj.getPartRecByPK(id) self.disk_key = None self.part_key = None self.provision = self.database.getAppglobals('PROVISION') schema = PartSchemaObj.schema self.validationHelper =_screenValidationHelper( unmountable_partitions = ['physical volume','software RAID',\ 'linux-swap']) self.kusu_specific_partitions = ['physical volume','software RAID','ntfs','fat32'] self.__mntStore = self.validationHelper.NO_MNTPT if id != None: #we're editing - get the disk_key we belong to done = False for disk_key in schema['disk_dict']: for part_key in schema['disk_dict'][disk_key]['partition_dict']: part_dict = schema['disk_dict'][disk_key]['partition_dict'][part_key] if part_dict['instid'] == id: self.disk_key = disk_key self.part_key = part_key assert(self.partition == part_dict) done = True break if done: break def setCallbacks(self): self.buttonsDict['ok_button'].setCallback_(self.doOkAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) def doOkAction(self): #perform preparation required for validation and subsequent formAction return NAV_FORWARD def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def fsToggleMountPoint(self): #update validation self.validationHelper.current_filesystem =\ self.filesystem.current() # we use a global field to store the cached value # this only works because we can only edit 1 partition at a time if self.filesystem.current() in\ self.validationHelper.unmountable_partitions: self.mountpoint.setEntry(self.validationHelper.NO_MNTPT) self.mountpoint.setEnabled(False) else: self.mountpoint.setEnabled(True) #reset to the old mountpoint if self.__mntStore != self.validationHelper.NO_MNTPT: self.mountpoint.setEntry(self.__mntStore) def mountpointChange(self): self.__mntStore = self.mountpoint.value() def drawImpl(self): self.screenGrid = snack.Grid(1, 5) #mntpnt self.mountpoint = kusuwidgets.LabelledEntry('Mount Point:', 20, text="", hidden=0, password=0, scroll=0, returnExit=0 ) if self.partition and self.partition['mountpoint']: self.mountpoint.setEntry(self.partition['mountpoint']) self.mountpoint.setCallback(self.mountpointChange) self.mountpoint.addCheck(self.validationHelper.mountPointCheck) #fs self.filesystem = kusuwidgets.ColumnListbox(2, colWidths=[20], colLabels=['File System type:'], justification=[LEFT], returnExit=0) self.filesystem.setCallback_(self.fsToggleMountPoint) fs_types = self.PartSchemaObj.disk_profile.fsType_dict.keys() if self.provision != 'KUSU': fs_types = [fs for fs in fs_types if fs not in self.kusu_specific_partitions ] fs_types.sort() for fs in fs_types: self.filesystem.addRow([fs], fs) if self.partition and self.partition['fs'] and self.partition['fs'] in fs_types: self.filesystem.setCurrent(self.partition['fs']) #disks self.drives = kusuwidgets.ColumnListbox(2, colWidths=[20], colLabels=['Allowable Drives:'], justification=[LEFT], returnExit=0) disks = self.PartSchemaObj.schema['disk_dict'].keys() disks = ['Disk %s' %x for x in disks] for disk in disks: self.drives.addRow([disk], disk) if self.disk_key: self.drives.setCurrent('Disk %s' %self.disk_key) #size fill = False size = '' if self.partition: fill = self.partition['fill'] size = str(self.partition['size_MB']) self.fixed_size = snack.SingleRadioButton('Fixed Size (MB):', None, isOn=not fill) self.fixed_size_entry = snack.Entry(7) self.min_size = snack.SingleRadioButton('Fill at least (MB):', self.fixed_size, isOn = fill) self.min_size_entry = snack.Entry(7) if fill: self.min_size_entry.set(size) else: self.fixed_size_entry.set(size) #preserve isOn = 0 if self.provision == 'KUSU' and self.partition and self.partition['preserve'] == 1: isOn = 1 self.do_not_format_partition = snack.Checkbox('Do not format partition', isOn = isOn) # position widgets on screen self.screenGrid.setField(self.mountpoint, 0,0, padding=(0,0,0,1)) subgrid = snack.Grid(2,1) subgrid.setField(self.filesystem, 0,0, padding=(0,0,2,0)) if self.provision =='KUSU': subgrid.setField(self.drives, 1,0, padding=(2,0,0,0)) self.screenGrid.setField(subgrid, 0,1) subgrid = snack.Grid(2,1) subgrid.setField(self.fixed_size, 0,0) subgrid.setField(self.fixed_size_entry, 1,0) self.screenGrid.setField(subgrid, 0,2, anchorLeft=1, padding=(0,1,0,0)) subgrid = snack.Grid(2,1) subgrid.setField(self.min_size, 0,0) subgrid.setField(self.min_size_entry, 1,0) self.screenGrid.setField(subgrid, 0,3, anchorLeft=1) if self.provision == 'KUSU': self.screenGrid.setField(self.do_not_format_partition, 0,4, padding=(0,1,0,1)) def collectFieldsIntoRec(self): partDataRec = PartDataRec() if self.partition: partDataRec.idpartitions = self.partition['instid'] else: partDataRec.idpartitions = self.PartSchemaObj.getNewPartId() # mntpnt + fstype partDataRec.mntpnt = self.mountpoint.value().strip() partDataRec.fstype = self.filesystem.current() # device if self.drives.current() == None: #creating 1st partition partDataRec.device = 1 else: partDataRec.device = int(self.drives.current().split()[1].strip()) ## check if device is an existing spanning PV if self.partition: if self.PartRec['device'].lower() == 'n': partDataRec.device = 'N' # fill partDataRec.fill = 0 if self.min_size.selected(): partDataRec.fill = 1 # size size = None if self.min_size.selected(): size = self.min_size_entry.value() elif self.fixed_size.selected(): size = self.fixed_size_entry.value() partDataRec.size = size #preserve partDataRec.preserve = 0 if self.do_not_format_partition.value(): partDataRec.preserve = 1 return partDataRec def validate(self): """Validation code goes here. Activated when 'Next' button is pressed.""" # UI specific validation retval,msg = self.mountpoint.verify() if not retval: if msg: return False,msg #if NO_MNTPT is not '', then the validation check handles it elif self.filesystem.current() in\ self.validationHelper.unmountable_partitions: pass # None,None returned for swap etc else: return False,'Please specify a mount point' if self.min_size.selected(): retval,msg = self.validationHelper.isAPositiveNumberCheck( self.min_size_entry.value()) if not retval: if msg: return False, ' '.join([msg,'for the minimum partition size']) if self.fixed_size.selected(): retval,msg = self.validationHelper.isAPositiveNumberCheck( self.fixed_size_entry.value()) if not retval: return False, ' '.join([msg,'for the fixed partition size']) # Collect all field values partDataRec = self.collectFieldsIntoRec() # Validate try: tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG.setPartitionSchema(self.PartSchemaObj) tmpNG.validatePartition(self.database, partDataRec) except NGEValidationError, e: return False, str(e) return True, 'Success' def formAction(self): ''' Actions taken once the form data was successfully validated. Timeline: Next_callback, validation, formAction ''' # Collect all field values partDataRec = self.collectFieldsIntoRec() # Determine whether to have this PV span all disks if partDataRec.fstype == "physical volume": pvmap = self.PartSchemaObj.getPVMap() spanningPVExists = False for k in pvmap.keys(): p = self.PartSchemaObj.getPartRecByPK(k) if p['device'] == 'N': spanningPVExists = True break if not spanningPVExists: msg = 'Do you want this PV to span all disks?' pv_span = self.selector.popupYesNo('Spanning PV', msg, defaultNo=True) if pv_span: partDataRec.device = 'N' # Add/Edit Partition tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG.setPartitionSchema(self.PartSchemaObj) tmpNG.editPartition(self.database, partDataRec) kl.debug("scrPartSchema_PartEdit: \npartrec = %s\nvg_dict = %s\ndisk_dict = %s" \ %(str(partDataRec),str(self.PartSchemaObj.schema['vg_dict']), str(self.PartSchemaObj.schema['disk_dict'])) ) class scrPartSchema_LVEdit(USXBaseScreen): ''' This screen class allows to edit existing or create new Logical Volumes. It exposes PartSchemaObj to the user for modification. ''' name = "Logical Volume Modification" buttons = [ 'ok_button', 'cancel_button' ] def __init__(self, PartSchemaObj, id=None, database=None, kusuApp=None,gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.PartSchemaObj = PartSchemaObj #passed by ref. self.partition = PartSchemaObj.getDictByPK(id) self.vg_name = None self.lv_name = None schema = PartSchemaObj.schema self.validationHelper =_screenValidationHelper( unmountable_partitions = ['linux-swap']) self.__mntStore = self.validationHelper.NO_MNTPT self.vg_changed = False if id != None: #we're editing - get the vgname we belong to done = False for vg in schema['vg_dict']: for lvname in schema['vg_dict'][vg]['lv_dict']: lv_dict = schema['vg_dict'][vg]['lv_dict'][lvname] if lv_dict['instid'] == id: self.vg_name = vg self.lv_name = lvname assert(self.partition == lv_dict) done = True break if done: break def setCallbacks(self): self.buttonsDict['ok_button'].setCallback_(self.doOkAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) def doOkAction(self): #perform preparation required for validation and subsequent formAction return NAV_FORWARD def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def fsToggleMountPoint(self): # we use a global field to store the cached value # this only works because we can only edit 1 partition at a time self.validationHelper.current_filesystem = self.filesystem.current() if self.filesystem.current() in\ self.validationHelper.unmountable_partitions: self.mountpoint.setEntry(self.validationHelper.NO_MNTPT) self.mountpoint.setEnabled(False) else: self.mountpoint.setEnabled(True) #reset to the old mountpoint if self.__mntStore != self.validationHelper.NO_MNTPT: self.mountpoint.setEntry(self.__mntStore) def mountpointChange(self): self.__mntStore = self.mountpoint.value() def drawImpl(self): self.screenGrid = snack.Grid(1, 5) lbllst = ['Mount Point:', 'Logical Volume Name:', 'Size (MB):'] justval = max([len(x) for x in lbllst]) #mntpnt self.mountpoint = kusuwidgets.LabelledEntry(lbllst[0].rjust(justval), 20, text="", hidden=0, password=0, scroll=0, returnExit=0 ) if self.partition and self.partition['mountpoint']: self.mountpoint.setEntry(self.partition['mountpoint']) self.mountpoint.setCallback(self.mountpointChange) self.mountpoint.addCheck(self.validationHelper.mountPointCheck) #lvname self.lvname = kusuwidgets.LabelledEntry( lbllst[1].rjust(justval), 20, text="", hidden=0, password=0, scroll=0, returnExit=0 ) if self.partition: if self.lv_name: self.lvname.setEntry(self.lv_name) self.lvname.setEnabled(False) self.size = kusuwidgets.LabelledEntry(lbllst[2].rjust(justval), 20, text="", hidden=0, password=0, scroll=0, returnExit=0 ) self.size.addCheck(self.validationHelper.isAPositiveNumberCheck) if self.partition: self.size.setEntry(str(self.partition['size_MB'])) self.filesystem = kusuwidgets.ColumnListbox(2, colWidths=[20], colLabels=['File System type:'], justification=[LEFT], returnExit=0) fs_types = self.PartSchemaObj.disk_profile.fsType_dict.keys() fs_types.sort() [self.filesystem.addRow([fs], fs) for fs in fs_types if fs not in\ ['physical volume','software RAID','ntfs','fat32']] self.filesystem.setCallback_(self.fsToggleMountPoint) if self.partition and self.partition['fs'] and self.partition['fs'] in fs_types: self.filesystem.setCurrent(self.partition['fs']) self.volumegroup = kusuwidgets.ColumnListbox(2, colWidths=[20], colLabels=['Volume Group:'], justification=[LEFT]) for vg_key in self.PartSchemaObj.schema['vg_dict']: self.volumegroup.addRow([vg_key],vg_key) if self.partition and self.vg_name: self.volumegroup.setCurrent(self.vg_name) #preserve isOn = 0 if self.partition and self.partition['preserve'] == 1: isOn = 1 self.do_not_format_partition = snack.Checkbox('Do not format partition',isOn=isOn) #position widgets on the screen self.screenGrid.setField(self.mountpoint, 0,0) self.screenGrid.setField(self.lvname, 0,1) self.screenGrid.setField(self.size, 0,2) subgrid = snack.Grid(2,1) subgrid.setField(self.filesystem, 0,0, padding=(0,0,2,0)) subgrid.setField(self.volumegroup, 1,0, padding=(2,0,0,0)) self.screenGrid.setField(subgrid, 0,3, padding=(0,1,0,1)) self.screenGrid.setField(self.do_not_format_partition, 0,4, padding=(0,0,0,1)) def collectFieldsIntoRec(self): lvDataRec = LVDataRec() if self.partition: lvDataRec.idpartitions = self.partition['instid'] else: lvDataRec.idpartitions = self.PartSchemaObj.getNewPartId() lvDataRec.mntpnt = self.mountpoint.value().strip() lvDataRec.fstype = self.filesystem.current() lvDataRec.size = self.size.value().strip() lvDataRec.device = self.lvname.value().strip() # preserve lvDataRec.preserve = 0 if self.do_not_format_partition.value(): lvDataRec.preserve = 1 # volgrp id vgname = self.volumegroup.current() if vgname != None: vgdict = self.PartSchemaObj.schema['vg_dict'] if vgdict.has_key(vgname): vgid = vgdict[vgname]['instid'] lvDataRec.vol_group_id = vgid return lvDataRec def validate(self): # UI Specific validation retval,msg = self.mountpoint.verify() if not retval: if msg: return False,msg #if NO_MNTPT is not '', then the validation check handles it elif self.filesystem.current() in\ self.validationHelper.unmountable_partitions: pass # None,None returned for swap etc else: return False,'Please specify a mount point' retval,msg = self.size.verify() if not retval: if msg: return False, ' '.join([msg,'for the partition size']) else: return False, 'Please enter a size for the partition' lvDataRec = self.collectFieldsIntoRec() # Validate try: tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG.setPartitionSchema(self.PartSchemaObj) tmpNG.validateLV(self.database, lvDataRec) except NGEValidationError, e: return False, str(e) # Additional UI Validaton. Can only be performed after basic checks are done #grab the lv dict and see if there are any duplicates if its a new partition # ignore this check if its an edit because lv names cannot be edited. if not self.partition or self.vg_name != self.volumegroup.current(): vgdict = self.PartSchemaObj.schema['vg_dict'] lvdict = vgdict[self.volumegroup.current()]['lv_dict'] if self.lvname.value().strip() in lvdict.keys(): return False,"Logical volume name already defined in this group." return True, 'Success' def formAction(self): lvDataRec = self.collectFieldsIntoRec() # Add/Remove LV tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG.setPartitionSchema(self.PartSchemaObj) tmpNG.editLV(self.database, lvDataRec) class scrPartSchema_VGEdit(USXBaseScreen): ''' This screen class allows to edit existing or create new Volume Groups. It exposes PartSchemaObj to the user for modification. ''' name = "Logical Volume Modification" buttons = [ 'ok_button', 'cancel_button' ] def __init__(self, PartSchemaObj, id=None, database=None, kusuApp=None,gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.PartSchemaObj = PartSchemaObj #passed by ref. self.partition = PartSchemaObj.getDictByPK(id) self.PartRec = PartSchemaObj.getPartRecByPK(id) def setCallbacks(self): self.buttonsDict['ok_button'].setCallback_(self.doOkAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) def doOkAction(self): #perform preparation required for validation and subsequent formAction return NAV_FORWARD def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def drawImpl(self): self.screenGrid = snack.Grid(1, 4) lbllst = ['Volume Group Name:', 'Physical Extent (MB):'] justval = max([len(x) for x in lbllst]) self.vg_name = kusuwidgets.LabelledEntry( lbllst[0].rjust(justval), 21, text="", hidden=0, password=0, scroll=0, returnExit=0 ) if self.partition: self.vg_name.setEnabled(False) self.vg_name.setEntry(self.partition['name']) self.phys_extent = kusuwidgets.LabelledEntry( lbllst[1].rjust(justval), 21, text="32", hidden=0, password=0, scroll=0, returnExit=0 ) self.phys_extent.setEnabled(False) self.phys_to_use_lbl = snack.Label('Physical Volumes to Use:') self.phys_to_use = snack.CheckboxTree(height=3, scroll=1) #populate with available PVs PVidmap = self.PartSchemaObj.getPVMap() for k,v in PVidmap.items(): if (not self.partition and v) or \ (self.partition and v and\ v != self.partition['name']): #this pv is associated with another VolGrp continue isOn = False if self.partition and v and v == self.partition['name']: isOn = True #determine the disk & partition to display for disk_key in self.PartSchemaObj.schema['disk_dict']: part_dict = self.PartSchemaObj.schema['disk_dict'][disk_key]['partition_dict'] for part_key in part_dict.keys(): partition = part_dict[part_key] if partition['instid'] == k: self.phys_to_use.append("d%sp%s" %(disk_key,part_key), item=k, selected=isOn) #position widgets on the screen self.screenGrid.setField(self.vg_name, 0,0) self.screenGrid.setField(self.phys_extent, 0,1) self.screenGrid.setField(self.phys_to_use_lbl, 0,2) self.screenGrid.setField(self.phys_to_use, 0,3) def collectFieldsIntoRec(self): vgDataRec = VGDataRec() if self.partition: vgDataRec.idpartitions = self.partition['instid'] else: vgDataRec.idpartitions = self.PartSchemaObj.getNewPartId() vgDataRec.device = self.vg_name.value().strip() vgDataRec.size = self.phys_extent.value() vgDataRec.phys_vols = self.phys_to_use.getSelection() return vgDataRec def validate(self): vgDataRec = self.collectFieldsIntoRec() # Validate try: tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG.setPartitionSchema(self.PartSchemaObj) tmpNG.validateVG(self.database, vgDataRec) except NGEValidationError, e: return False, str(e) return True, 'Success' def formAction(self): vgDataRec = self.collectFieldsIntoRec() tmpNG = NodeGroup(ngid=curNG['ngid']) tmpNG.setPartitionSchema(self.PartSchemaObj) tmpNG.editVG(self.database, vgDataRec) class scrNGEEdit_Finalize(USXBaseScreen): name = 'Summary of Changes' buttons = [ 'accept_button', 'back_button', 'cancel_button' ] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.__diffNG = None self.__plugdir = '/opt/kusu/lib/plugins/ngedit' #FIXME: may need to change def setCallbacks(self): #for the buttons self.buttonsDict['accept_button'].setCallback_(self.doAcceptAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) self.hotkeysDict['F8'] = self.doAcceptAction def doBackAction(self): return NAV_BACK def doCancelAction(self): removeNGLockFile(curNG['ngid']) return NAV_QUIT def doAcceptAction(self): return NAV_FORWARD def drawImpl(self): self.__diffNG = curNG - origNG diffNG = self.__diffNG msg = curNG.summarizeChanges(self.database, origNG) self.screenGrid = snack.Grid(1, 1) self.screenGrid.setField(snack.TextboxReflowed(width=MAXWIDTH, maxHeight=10, text=msg), 0,0, (0,0,0,0), anchorLeft=1) def validate(self): return True, 'Success' def formAction(self): windowInst = self try: curNG.commitChanges(self.database, origNG, self.kusuApp, windowInst) except NGECommitError, e: self.selector.popupMsg("Node Group Update Error", str(e)) removeNGLockFile(curNG['ngid']) return NAV_QUIT # Commit is done. Release the lock file. removeNGLockFile(curNG['ngid']) class scrNGEEdit_End(USXBaseScreen): name = 'cfmsync Update' msg = 'Update nodegroup with cfmsync' buttons = ['yes_button', 'no_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) def setCallbacks(self): self.buttonsDict['yes_button'].setCallback_(self.doYesAction) self.buttonsDict['no_button'].setCallback_(self.doNoAction) def doYesAction(self): if 'ice' == self.database.getAppglobals('PROVISION').lower(): self.genKS() curNG.syncNodes(self, True) return NAV_FORWARD def genKS(self): output = curNG.genKickstart(self,True) ngid = curNG.PKval autoinst = self.database.getAppglobals('DEPOT_AUTOINST_ROOT') ngpath = os.path.join(autoinst, str(ngid)) if not os.path.exists(ngpath) : os.makedirs(ngpath) nname = curNG['ngname'] # Convert ngname to an ICE acceptable version r = re.compile('[^\w.-]') nname = r.sub('_',curNG['ngname']) ksfile = os.path.join(ngpath, '%s.cfg' % nname) ks = open(ksfile,'w') # overwrite ks.write(output) ks.close() # Regenerate ICE repository file when repos is changed if curNG['repoid'] <> origNG['repoid']: icle_path = '/opt/repository/.icle' os.system("genconfig ICEintegration instconf '%s' > %s/%s.xml" % (curNG['ngname'], icle_path, nname)) def doNoAction(self): if 'ice' == self.database.getAppglobals('PROVISION').lower(): self.genKS() curNG.syncNodes(self, False) return NAV_FORWARD def drawImpl(self): self.screenGrid = snack.Grid(1, 1) lblheader = snack.Label("Do you wish to update the nodes in this nodegroup now?") self.screenGrid.setField(lblheader, 0,0, padding=(0,0,0,0), growx=1, anchorLeft=1) class scrNGEEdit_Scripts(USXBaseScreen): name = 'Custom Scripts' msg = 'Enter the custom script path' buttons = ['next_button', 'back_button','add_button', 'delete_button', 'cancel_button'] def __init__(self, database, kusuApp=None, gridWidth=45): USXBaseScreen.__init__(self, database, kusuApp=kusuApp, gridWidth=gridWidth) self.__allscrpath = database.getAppglobals('DEPOT_REPOS_SCRIPTS') if not self.__allscrpath: self.__allscrpath = '/depot/repos/custom_scripts' self.__copiedfiles = [] def setCallbacks(self): #for the buttons self.buttonsDict['next_button'].setCallback_(self.doForwardAction) self.buttonsDict['back_button'].setCallback_(self.doBackAction) self.buttonsDict['cancel_button'].setCallback_(self.doCancelAction) self.buttonsDict['add_button'].setCallback_(self.doAddAction) self.buttonsDict['delete_button'].setCallback_(self.doDeleteAction) #for hotkeys self.hotkeysDict['F12'] = self.F12Action self.hotkeysDict['F8'] = self.doForwardAction self.hotkeysDict['F5'] = self.doBackAction def F12Action(self): self.selector.popupMsg("Internal State", "curNG['scripts']=%s\ncopiedfiles= %s" \ %(str(curNG['scripts']), str(self.__copiedfiles))) return NAV_IGNORE def doCancelAction(self): for curscr in self.__copiedfiles: os.remove(os.path.join(self.__allscrpath,curscr)) self.__copiedfiles = [] removeNGLockFile(curNG['ngid']) return NAV_QUIT def doForwardAction(self): return NAV_FORWARD def doBackAction(self): return NAV_BACK def doDeleteAction(self): if not self.__curscr: return NAV_NOTHING curscr = self.__curscr.current() assert(curscr in curNG['scripts']) del curNG['scripts'][curNG['scripts'].index(curscr)] if curscr in self.__copiedfiles: #it's been copied in the current editing session os.remove(os.path.join(self.__allscrpath,curscr)) del self.__copiedfiles[self.__copiedfiles.index(curscr)] return NAV_NOTHING def doAddAction(self): newscr = self.__newscr.value().strip() if not newscr: self.selector.popupMsg("Invalid script specification", "Please enter script location") return NAV_NOTHING # Copy the new script try: copiedScripts = copyScripts(self.database,[newscr]) if copiedScripts: # Newly copied script newScript = copiedScripts[0] self.__copiedfiles.append(newScript) else: # Existing script newScript = newscr except NGEValidationError, e: self.selector.popupMsg("Script Copy Error",str(e)) return NAV_NOTHING #update curNG if not curNG['scripts']: curNG['scripts'] = [] if not newScript in curNG['scripts']: curNG['scripts'].append(newScript) return NAV_NOTHING def drawImpl(self): self.screenGrid = snack.Grid(1, 2) labelTxt = 'New Script: ' w = self.gridWidth if curNG['scripts']: w = max([len(x) for x in curNG['scripts']] + [MAXWIDTH*3/4]) self.__curscr = snack.Listbox(height=10, scroll=1, width=w,returnExit=1, showCursor=0) for script in sorted(curNG['scripts']): self.__curscr.append(script, item=script) else: self.__curscr = None self.__newscr = kusuwidgets.LabelledEntry(labelTxt=labelTxt, text="",width=w-len(labelTxt)) if self.__curscr: self.screenGrid.setField(self.__curscr, 0,0, anchorLeft=1) else: self.screenGrid.setField(snack.TextboxReflowed(text="Currently no scripts associated.", width=w),0,0, (0,0,0,1), anchorLeft=1) self.screenGrid.setField(self.__newscr, 0,1, anchorLeft=1) def validate(self): txt = self.__newscr.value().strip() if txt: return False, "Please leave the entry field empty to proceed to the next screen." return True, 'Success' def formAction(self): bIsDiskless = curNG['installtype'] == 'diskless' bPartNext = isinstance(self.selector.screens[self.selector.currentStep+1], scrNGEEdit_PartSchema) if bIsDiskless ^ bPartNext: return if bIsDiskless and bPartNext: #remove screen from sequence del self.selector.screens[self.selector.currentStep+1] else: #add screen to sequence self.selector.screens.insert(self.selector.currentStep+1,\ scrNGEEdit_PartSchema(database=self.database, kusuApp=self.kusuApp)) class NGEApp(KusuApp): def __init__(self): KusuApp.__init__(self) if os.getuid() != 0: self.errorMessage("nonroot_execution\n") sys.exit(-1) dbdriver = os.getenv('KUSU_DB_ENGINE', 'postgres') try: self.dbs = db.DB(dbdriver, 'kusudb', 'nobody') except UnsupportedDriverError, e: msg = 'Problems establishing database connection. Error: %s' % e self.logErrorEvent('Database error: ' + msg) sys.exit(1) self.__db = KusuDB() self.action = NGE_TUI self.ng = None self.ngnew = None self.xmlfile = None self.syncNG = False self.removeXML = False try: self.__db.connect(user='apache', dbname='kusudb') except Exception,msg: msg = 'Problems establishing database connection. Error: %s' %msg self.logErrorEvent(msg) sys.exit(1) # setup the CL parser self.parser.add_option('-d', '--delete',dest='ngdel', help=self._("ngedit_usage_delete")) self.parser.add_option('-c', '--copy', dest='ngsrc', help=self._("ngedit_usage_copy")) self.parser.add_option('-n', '--new', dest='ngnew', help=self._("ngedit_usage_new")) self.parser.add_option('-s', '--stale', dest='ngstale',help=self._("ngedit_usage_stale")) self.parser.add_option('-l', '--list', dest='list', help=self._("ngedit_usage_list"), action="store_true") self.parser.add_option('-p', '--print', dest='ngprint',help=self._("ngedit_usage_print")) # XML related options self.parser.add_option("-i", "--import", action="store", type="string", dest="ngimport", help=self._("ngedit_usage_import")) self.parser.add_option("-u", "--update", action="store_true", dest="ngupdate", help=self._("ngedit_usage_update")) self.parser.add_option("-r", "--removexml", action="store_true", dest="removexml", help=self._("ngedit_usage_removexml")) self.parser.add_option("-e", "--export", action="store", type="string", dest="ngexport", help=self._("ngedit_usage_export")) self.parser.add_option("-t", "--test", action="store", type="string", dest="ngtest", help=self._("ngedit_usage_test")) self.parser.add_option("-v", "--version", action="callback", callback=self.toolVersion, help=self._("Displays ngedit version")) def toolVersion(self, option, opt, value, parser): """ toolVersion() Prints out the version of the tool to screen. """ print "Ngedit Version %s" % self.version sys.exit(0) def printHelp(self): self.parser.print_help() def genKS(self, ngid, ksfile): ''' generate kickstart file for RHEL and autoinst for SLES ''' db = self.__db # first detect target os query = 'select r.repoid from nodegroups ng, repos r where \ ng.repoid = r.repoid and ng.ngid = %s' % ngid db.execute(query) repoid = db.fetchone() query_start = 'select rname, version, arch from kits, repos_have_kits where ' +\ 'repos_have_kits.repoid=%s and repos_have_kits.kid = kits.kid' % repoid if db.driver == 'mysql': query = query_start + ' and kits.isOS' db.execute(query) else: # postgres for now query = query_start + ' and kits."isOS"' db.execute(query, postgres_replace=False) full_os, ver, arch = db.fetchall()[0] ostype = re.compile('[a-z]+').findall(full_os)[0] target_os = (ostype, ver, arch) plugin = Dispatcher.get('inst_conf_plugin', os_tuple=target_os) os.system("genconfig %s %s > %s" % (plugin, ngid, ksfile)) def parse(self): ''' parse the CL, populate options dict, & define self.action ''' (options, args) = self.parser.parse_args() #print 'options = ', options #print 'args = ', args #print 'class(options) = ', options.__class__ if args: self.stderrMessage('ngedit: No arguments expected - encountered args = %s\n', string.join(args,', ')) self.printHelp() sys.exit(1) if bool(options.ngsrc) ^ bool(options.ngnew): #exactly one of them was specified self.stderrMessage('ngedit: both ngsrc and ngdst must be specified to copy a node group\n') self.printHelp() sys.exit(1) # Check import options if options.ngupdate and not bool(options.ngimport): # Update option can only be used with import self.stderrMessage('ngedit: update option can only be used with import option\n') self.printHelp() sys.exit(1) if options.removexml and not bool(options.ngimport): # Remove xml file option can only be used with import self.stderrMessage('ngedit: remove xml option can only be used with import option\n') self.printHelp() sys.exit(1) if not len([x for x in [bool(options.list), bool(options.ngdel), bool(options.ngsrc), bool(options.ngstale), bool(options.ngprint), bool(options.ngimport), bool(options.ngexport), bool(options.ngtest)] if x]) <= 1: self.stderrMessage('ngedit: Invalid combination of command-line options specified.\n') self.printHelp() sys.exit(1) if options.list: self.action = NGE_PRNALL elif options.ngdel: self.action = NGE_DEL self.ng = options.ngdel elif options.ngsrc: self.action = NGE_CPY self.ng = options.ngsrc self.ngnew = options.ngnew elif options.ngstale: self.action = NGE_PRNSTL self.ng = options.ngstale elif options.ngprint: self.action = NGE_PRNONE self.ng = options.ngprint elif options.ngimport: self.action = NGE_IMPORT self.xmlfile = options.ngimport self.syncNG = options.ngupdate self.removeXML = options.removexml elif options.ngexport: self.action = NGE_EXPORT self.ng = options.ngexport elif options.ngtest: self.action = NGE_TEST self.xmlfile = options.ngtest def removeXMLFile(self): if self.action == NGE_IMPORT and os.path.exists(self.xmlfile): self.stdoutMessage("Removing XML configuration file %s\n", self.xmlfile) os.unlink(self.xmlfile) def getActionDesc(self): if self.action == NGE_CPY: return "Copy nodegroup" elif self.action == NGE_DEL: return "Delete nodegroup" elif self.action == NGE_IMPORT: return "Import nodegroup" elif self.action == NGE_EXPORT: return "Export nodegroup" else: return KusuApp.getActionDesc(self) def run(self): if self.action == NGE_TUI: screenFactory = NGEScreenFactory([ scrNGEMain(database=self.__db, kusuApp=self), ]) ks = USXNavigator(screenFactory, screenTitle="Node Group Editor", showTrail=False) ks.run() elif self.action == NGE_CPY: eventStartMsg = "Copying node group %s to %s" % (self.ng,self.ngnew) eventDoneMsg = "Finished copying node group %s to %s" % (self.ng,self.ngnew) # Event Start self.logEvent(eventStartMsg, toStdout=False) query = "select ngid from nodegroups where ngname = '%s'" %self.ng self.__db.execute(query) rv = self.__db.fetchone() if not rv: msg = "nodegroup '%s' not found in the database" % self.ng self.logErrorEvent(msg) sys.exit(1) ngid = rv[0] if ngid==1: msg = "Installer node group cannot be copied" self.logErrorEvent(msg) sys.exit(1) elif ngid == 5: msg = "Unmanaged node group cannot be copied" self.logErrorEvent(msg) sys.exit(1) # Lock the selected node group if isNGLockFileExists(ngid): msg = "Copy not allowed on node group because it is being modified. " \ "If it is not, then delete %s." % getNGLockFile(ngid) self.logErrorEvent(msg) sys.exit(1) createNGLockFile(ngid) query = "select ngid from nodegroups where ngname = '%s'" %self.ngnew self.__db.execute(query) rv = self.__db.fetchone() if rv: msg = "The nodegroup '%s' already exists. Please change." % self.ngnew self.logErrorEvent(msg) removeNGLockFile(ngid) sys.exit(1) ngsrc = NodeGroup(ngid = ngid) ngsrc.syncFromDB(self.__db) ngsrc[ngsrc.PKfld] = None #give it new identity ngsrc['ngname'] = self.ngnew ngsrc.syncToDB(self.__db) if 'ice' == self.__db.getAppglobals('PROVISION').lower(): autoinst = self.__db.getAppglobals('DEPOT_AUTOINST_ROOT') ngpath = os.path.join(autoinst, str(ngsrc['ngid'])) if not os.path.exists(ngpath) : os.makedirs(ngpath) nname = ngsrc['ngname'] # Convert ngname to an ICE acceptable version r = re.compile('[^\w.-]') nname = r.sub('_',ngsrc['ngname']) # Generate kickstart file ksfile = os.path.join(ngpath, '%s.cfg' % nname) self.genKS(ngsrc['ngid'], ksfile) # Generate the XML for kickstart file icle_path = '/opt/repository/.icle' os.system("genconfig ICEintegration instconf '%s' > %s/%s.xml" % (ngsrc['ngname'], icle_path, nname)) #copy CFM directory srcNGcfmdir = os.path.join(CFMBaseDir, self.ng) dstNGcfmdir = os.path.join(CFMBaseDir, self.ngnew) if os.path.isdir(srcNGcfmdir): if not os.path.exists(dstNGcfmdir): #copy the tree shutil.copytree(srcNGcfmdir,dstNGcfmdir,True) else: self.stdoutMessage("ngedit: CFM directory for nodegroup "+\ "'%s' already exists. It was left intact.\n" %self.ngnew) # Run the cfmsync so nodes will install properly os.system("cfmsync -n '%s' -f" % self.ngnew) copyPackagelst(dbs=self.__db, src_ngid=ngid, dst_ngid=ngsrc['ngid']) removeNGLockFile(ngid) # Event Done self.logEvent(eventDoneMsg, toStdout=False) elif self.action == NGE_DEL: eventStartMsg = "Deleting node group %s" % (self.ng) eventDoneMsg = "Finished deleting node group %s" % (self.ng) # Event Start self.logEvent(eventStartMsg, toStdout=False) query = "select ngid from nodegroups where ngname= '%s'" %self.ng self.__db.execute(query) rv = self.__db.fetchone() if not rv: msg = "nodegroup '%s' not found in the database" %self.ng self.logErrorEvent(msg) sys.exit(1) ngid = rv[0] if 1<=ngid and ngid<=5: msg = "Deletion of reserved node groups is not permitted." self.logErrorEvent(msg) sys.exit(1) # Lock the selected node group if isNGLockFileExists(ngid): msg = "Delete not allowed on node group because it is being modified. " \ "If it is not, then delete %s." % getNGLockFile(ngid) self.logErrorEvent(msg) sys.exit(1) createNGLockFile(ngid) #delete only if no hosts using it query = "select nid from nodes where ngid = %s" % ngid self.__db.execute(query) rv = self.__db.fetchall() if len(rv) > 0: msg = "%s node(s) are still using nodegroup '%s'." \ " Deletion disallowed" % (len(rv),self.ng) self.logErrorEvent(msg) removeNGLockFile(ngid) sys.exit(1) #beauty ngdel = NodeGroup(ngid = ngid) ngdel.syncFromDB(self.__db) ngname = ngdel['ngname'] ngdel.eraseFromDB(self.__db) if 'ice' == self.__db.getAppglobals('PROVISION').lower(): autoinst = self.__db.getAppglobals('DEPOT_AUTOINST_ROOT') ngpath = os.path.join(autoinst, str(ngid)) if os.path.isdir(ngpath): shutil.rmtree(ngpath) # del kickstart file icle_path = '/opt/repository/.icle' r = re.compile('[^\w.-]') nname = r.sub('_',ngname) if os.path.isfile(os.path.join(icle_path, '%s.xml' % nname)): os.unlink(os.path.join(icle_path, '%s.xml' % nname)) # del kickstart xml file #purge the CFM directory delNGcfmdir = os.path.join(CFMBaseDir, ngdel['ngname']) if os.path.isdir(delNGcfmdir): shutil.rmtree(delNGcfmdir) cfmCleanup(self.__db, ngid) removeNGLockFile(ngid) # Event Done self.logEvent(eventDoneMsg, toStdout=False) elif self.action == NGE_PRNALL: self.__db.execute('select ngname,ngdesc,type from nodegroups') rv = self.__db.fetchall() rv = [ ifelse(None in x, [ ifelse(y==None,'',y) for y in x ] , list(x)) for x in rv ] for ngname,ngdesc,type in rv: print "%s : %s : %s" %(ngname,ngdesc,type) elif self.action == NGE_PRNSTL: self.__db.execute("select ngid from nodegroups where ngname = '%s'" %self.ng) rv = self.__db.fetchone() if not rv: self.stderrMessage("ngedit: nodegroup '%s' not found in the database\n", self.ng) sys.exit(1) ngid = rv[0] query = ''' select n.name, n.lastupdate, n.rack, n.rank from nodes n where n.state = 'Expired' and n.ngid = %s ''' %ngid self.__db.execute(query) rv = self.__db.fetchall() rv = [ ifelse(None in x, [ ifelse(y==None,'',y) for y in x ] , list(x)) for x in rv ] rv.insert(0, ('Name', 'Last updated', 'Rack', 'Rank')) for name,lastup,rack,rank in rv: print "%-20s | %-20s | %-4s | %s" %(name,lastup,rack,rank) elif self.action == NGE_PRNONE: self.__db.execute("select ngid from nodegroups where ngname = '%s'" %self.ng) rv = self.__db.fetchone() if not rv: self.stderrMessage("ngedit: nodegroup '%s' not found in the database\n", self.ng) sys.exit(1) ngid = rv[0] ngprn = NodeGroup(ngid = ngid) ngprn.syncFromDB(self.__db) ngprn.prettyPrint() #print repo reponame = None if ngprn['repoid']: self.__db.execute("select reponame from repos where repoid = %s " %ngprn['repoid']) rv = self.__db.fetchone() if rv: reponame = rv[0] justval = len("Nodename Format: ") print "Repository: ".ljust(justval), reponame #print inSync info query = "select nid from nodes where ngid = %s and state = 'Expired'" % ngid self.__db.execute(query) rv = self.__db.fetchall() inSync = True if len(rv) > 0: inSync = False print "In Sync: ".ljust(justval), inSync # print all the links now # oneliners: components, modules, packages, scripts compsstr = None if ngprn['comps'] : ngprn['comps'] = [ int(x) for x in ngprn['comps'] ] query = ''' select k.rname,c.cname,c.cdesc from kits k, components c where c.kid = k.kid and c.cid in %s ''' %seq2tplstr(ngprn['comps']) self.__db.execute(query) rv = self.__db.fetchall() compsstr = '\n' for rname,cname,cdesc in rv: compsstr += "\t%s : %s : %s\n" %(rname,cname,cdesc) print "Components: ".ljust(justval), compsstr if ngprn['packs']: #assume min(columns) >= 80 ngprn['packs'].sort() packstr = '' for i in xrange(len(ngprn['packs'])): if i%2 == 0: packstr += '\t' + ngprn['packs'][i].ljust(36) else: packstr += ngprn['packs'][i].ljust(36)+'\n' print 'Packages:\n' + packstr else: print 'Packages: '.ljust(justval), None if ngprn['modules']: ngprn['modules'].sort() print 'Modules:\n\t', string.join(ngprn['modules'], ', ') else: print 'Modules: '.ljust(justval), None if ngprn['scripts']: ngprn['scripts'].sort() print 'Scripts:\n\t', string.join(ngprn['scripts'], ', ') else: print 'Scripts: '.ljust(justval), None #extended info on: networks, partition schema #for partitions follow the TUI case: build schema and pretty print if ngprn['parts']: #work around Kusu bug 347 for p in ngprn['parts']: if p['options'] == None: p['options'] = '' if p['mntpnt'] == None: p['mntpnt'] = '' if p['device'] == None: p['device'] = '' provision = self.__db.getAppglobals('PROVISION') if provision == 'KUSU': schemaObj = PartSchema() schemaObj.mycreateSchema(ngprn['parts']) print "Partition Schema:" str2display = str(schemaObj) strlst = ['\t'+x for x in string.split(str2display,'\n')] print string.join(strlst,'\n') else: schemaObj = PartSchema() schema = schemaObj.mycreateSchema(ngprn['parts']) colLabels=['Device', 'Size(MB) ', 'Type ', 'Mount Point '] colWidths=[15,15,15,15] justification=[LEFT, RIGHT, LEFT, LEFT] txt = schemaObj.createRowText(['a','b','c','d'], colWidths, justification) str2display = '' disk_keys = schema['disk_dict'].keys() for key in sorted(disk_keys): # display device device = schema['disk_dict'][key] parts_dict = device['partition_dict'] parts_keys = parts_dict.keys() txt = schemaObj.createRowText(['Disk '+str(key), '', '', ''], colWidths, justification) str2display += txt + '\n' for part_key in sorted(parts_keys): partition = parts_dict[part_key] fs_type = partition['fs'] mountpoint = partition['mountpoint'] if fs_type == 'physical volume': partrec = schemaObj.getPartRecByPK(partition['instid']) (ispv,vg) = translatePartitionOptions(partrec['options'],'pv') if ispv and vg: mountpoint = vg part_devicename = ' ' +'d'+ str(key) +'p'+ str(part_key) txt = schemaObj.createRowText([part_devicename, str(partition['size_MB']), fs_type, mountpoint], colWidths, justification) str2display += txt + '\n' print "Partition Schema:" strlst = ['\t'+x for x in string.split(str2display,'\n')] print string.join(strlst,'\n') else: print "Partition Schema:".ljust(justval), None if ngprn['nets']: cols = [ ['Device','Network','Subnet','Description'] ] self.__db.execute('''select max(char_length(device)),max(char_length(network)) , max(char_length(subnet)), max(char_length(netname)) from networks''') rv = self.__db.fetchone() cols.append([int(x) for x in rv]) #justification values headstr = '' for i in xrange(len(cols[0])): #for all columns cols[1][i] = max(cols[1][i], len(cols[0][i]))+1 #make room for the label headstr += cols[0][i].ljust(cols[1][i]) headstr = headstr[:MAXWIDTH] print "Networks:\n\t", headstr if self.__db.driver == 'mysql': query = "select device, IFNULL(network, 'DHCP'), IFNULL(subnet, 'DHCP'), IFNULL(netname, ''), netid from networks" else: query = "select device, COALESCE(network, 'DHCP'), COALESCE(subnet, 'DHCP'), COALESCE(netname, ''), netid from networks" self.__db.execute(query) rv = self.__db.fetchall() for record in rv: record = list(record) #convert to list netid = record.pop() if not netid in ngprn['nets']: # Only display networks for which this nodegroup is # a member of continue entrystr = '' for i in xrange(len(record)): #construct an entry string entrystr += record[i].ljust(cols[1][i]) if len(entrystr) > MAXWIDTH: entrystr = entrystr[:MAXWIDTH - len('...')] + '...' print '\t' + entrystr else: print "Networks: ".ljust(justval), None elif self.action == NGE_IMPORT: xmlfile = self.xmlfile currNG = NodeGroupXMLRecord() # Remove the XML file if requested by user upon exit import atexit if self.removeXML: atexit.register(self.removeXMLFile) # Get node group name to create event log messages try: ngname = currNG.getNGNameFromXMLFile(xmlfile) except NGEXMLParseError,e: self.logErrorEvent(e) sys.exit(1) eventStartMsg = "Importing configuration for node group: %s" % ngname eventDoneMsg = "Finished importing configuration for node group: %s" % ngname # Event Start self.logEvent(eventStartMsg, toStdout=False) try: currNG.processXMLFile(xmlfile, self.__db) except (NGEXMLParseError,NGEValidationError),e: self.logErrorEvent(e) sys.exit(1) ngid = currNG['ngid'] # Lock the selected node group if isNGLockFileExists(ngid): msg = "Import not allowed on node group because it is being modified. " \ "If it is not, then delete %s." % getNGLockFile(ngid) self.logErrorEvent(msg) sys.exit(1) createNGLockFile(ngid) prevNG = NodeGroup(ngid=currNG['ngid']) prevNG.syncFromDB(self.__db) reposHasChanged = False # Handles default initrd and kernel and component associations for the new repos if prevNG['repoid'] <> currNG['repoid']: reposHasChanged = True curRepoId = currNG['repoid'] currNG = NodeGroup(ngid=currNG['ngid']) currNG.syncFromDB(self.__db) currNG['repoid'] = curRepoId try: currOS = self.dbs.Repos.selectone_by(repoid=int(currNG['repoid'])).os except ValueError: msg = "Repository id '%s' could not be converted to integer" % currNG['repoid'] self.logErrorEvent('Validation error: ' + msg) os_tup = (currOS.name, currOS.major+'.'+currOS.minor, currOS.arch) # Set the default initrd and kernel for the new repos currNG['kernel'] = "kernel-%s-%s.%s-%s" % (currOS.name , currOS.major, currOS.minor , currOS.arch) currNG['initrd'] = "initrd-%s-%s.%s-%s.img" % (currOS.name , currOS.major, currOS.minor, currOS.arch) currNG['kparams'] = Dispatcher.get('kparams', default='', os_tuple=os_tup) if currNG['installtype'] == 'disked': currNG['packs'] = Dispatcher.get('imaged_packages', default=[], os_tuple=os_tup) currNG['modules'] = Dispatcher.get('imaged_modules', default=[], os_tuple=os_tup) elif currNG['installtype'] == 'diskless': currNG['packs'] = Dispatcher.get('diskless_packages', default=[], os_tuple=os_tup) currNG['modules'] = Dispatcher.get('diskless_modules', default=[], os_tuple=os_tup) # Handle the component associations for the new repos currRepo = self.dbs.Repos.get(curRepoId) osKit = currRepo.getOSKit() osKit_comp = osKit.components[0].cid components_list = currRepo.getEligibleComponents(currNG['type']) currNG['comps'] = [comp.cid for comp in components_list if comp.cid in prevNG['comps']] currNG['comps'].append(osKit_comp) title = "Summary of Changes:\n" self.stdoutMessage(title) self.stdoutMessage("=" * len(title[:-1]) + "\n") self.stdoutMessage(currNG.summarizeChanges(self.__db, prevNG) + "\n") windowInst = None try: currNG.commitChanges(self.__db, prevNG, self, windowInst) # Regenerate the autoinst for SLES and kickstart for other os if 'ice' == self.__db.getAppglobals('PROVISION').lower(): autoinst = self.__db.getAppglobals('DEPOT_AUTOINST_ROOT') ngpath = os.path.join(autoinst, str(currNG['ngid'])) if not os.path.exists(ngpath) : os.makedirs(ngpath) nname = currNG['ngname'] # Convert ngname to an ICE acceptable version r = re.compile('[^\w.-]') nname = r.sub('_',currNG['ngname']) # Generate kickstart file ksfile = os.path.join(ngpath, '%s.cfg' % nname) self.genKS(currNG['ngid'], ksfile) # Generate the ICE repository file when repos is changed if reposHasChanged: icle_path = '/opt/repository/.icle' os.system("genconfig ICEintegration instconf '%s' > %s/%s.xml" % (currNG['ngname'], icle_path, nname)) if currNG.hasChanged(prevNG): self.stdoutMessage("Finished committing changes.\n") except NGECommitError,e: self.stdoutMessage("Error committing changes: %s\n" % str(e)) self.logErrorEvent(e) removeNGLockFile(ngid) sys.exit(1) if currNG.syncNodesIsRequired(prevNG): if self.syncNG: currNG.syncNodes(windowInst, True) else: currNG.syncNodes(windowInst, False) removeNGLockFile(ngid) # Event Done self.logEvent(eventDoneMsg, toStdout=False) elif self.action == NGE_EXPORT: self.__db.execute("select ngid from nodegroups where ngname = '%s'" % self.ng) rv = self.__db.fetchone() if not rv: self.stderrMessage("ngedit: nodegroup '%s' not found in the database\n" % self.ng) sys.exit(1) ngid = rv[0] if ngid == 5: self.stderrMessage("ngedit: Exporting of unmanaged node group configuration " \ "to XML is not allowed.\n") sys.exit(1) currNG = NodeGroupXMLRecord(ngid = ngid) self.stdoutMessage(currNG.syncFromDBAndDump2XML(self.__db)) elif self.action == NGE_TEST: xmlfile = self.xmlfile currNG = NodeGroupXMLRecord() try: currNG.processXMLFile(xmlfile, self.__db, testMode=True) except (NGEXMLParseError,NGEValidationError),e: self.stderrMessage("%s\n" % e) sys.exit(1) self.stdoutMessage("ngedit: input was successfully validated.\n") if __name__ == '__main__': # FIXME: This messy import is due to a workaround for #127670. from kusu.ngedit.constants import NGE_TUI NGEinst = NGEApp() from kusu.ngedit.ngedit import * from kusu.ngedit.partition import * NGEinst.parse() NGEinst.run()