12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359 |
- # coding: utf-8
- # Copyright (C) 1994-2018 Altair Engineering, Inc.
- # For more information, contact Altair at www.altair.com.
- #
- # This file is part of the PBS Professional ("PBS Pro") software.
- #
- # Open Source License Information:
- #
- # PBS Pro is free software. You can redistribute it and/or modify it under the
- # terms of the GNU Affero General Public License as published by the Free
- # Software Foundation, either version 3 of the License, or (at your option) any
- # later version.
- #
- # PBS Pro 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 Affero General Public License for more details.
- #
- # You should have received a copy of the GNU Affero General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- #
- # Commercial License Information:
- #
- # For a copy of the commercial license terms and conditions,
- # go to: (http://www.pbspro.com/UserArea/agreement.html)
- # or contact the Altair Legal Department.
- #
- # Altair’s dual-license business model allows companies, individuals, and
- # organizations to create proprietary derivative works of PBS Pro and
- # distribute them - whether embedded or bundled with other software -
- # under a commercial license agreement.
- #
- # Use of Altair’s trademarks, including but not limited to "PBS™",
- # "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
- # trademark licensing policies.
- import sys
- import os
- import socket
- import pwd
- import grp
- import logging
- import time
- import re
- import random
- import string
- import tempfile
- import cPickle
- import copy
- import datetime
- import traceback
- import threading
- from operator import itemgetter
- from collections import OrderedDict
- from distutils.version import LooseVersion
- try:
- import psycopg2
- PSYCOPG = True
- except:
- PSYCOPG = False
- try:
- from ptl.lib.pbs_ifl import *
- API_OK = True
- except:
- try:
- from ptl.lib.pbs_ifl_mock import *
- except:
- sys.stderr.write("failed to import pbs_ifl, run pbs_swigify " +
- "to make it\n")
- raise ImportError
- API_OK = False
- from ptl.lib.pbs_api_to_cli import api_to_cli
- from ptl.utils.pbs_dshutils import DshUtils
- from ptl.utils.pbs_procutils import ProcUtils
- from ptl.utils.pbs_cliutils import CliUtils
- from ptl.utils.pbs_fileutils import FileUtils, FILE_TAIL
- # suppress logging exceptions
- logging.raiseExceptions = False
- # Various mappings and aliases
- MGR_OBJ_VNODE = MGR_OBJ_NODE
- VNODE = MGR_OBJ_VNODE
- NODE = MGR_OBJ_NODE
- HOST = MGR_OBJ_HOST
- JOB = MGR_OBJ_JOB
- RESV = MGR_OBJ_RESV
- SERVER = MGR_OBJ_SERVER
- QUEUE = MGR_OBJ_QUEUE
- SCHED = MGR_OBJ_SCHED
- HOOK = MGR_OBJ_HOOK
- RSC = MGR_OBJ_RSC
- PBS_HOOK = MGR_OBJ_PBS_HOOK
- # the order of these symbols matters, see pbs_ifl.h
- (SET, UNSET, INCR, DECR, EQ, NE, GE, GT,
- LE, LT, MATCH, MATCH_RE, NOT, DFLT) = range(14)
- (PTL_OR, PTL_AND) = [0, 1]
- (IFL_SUBMIT, IFL_SELECT, IFL_TERMINATE, IFL_ALTER,
- IFL_MSG, IFL_DELETE, IFL_RALTER) = [0, 1, 2, 3, 4, 5, 6]
- (PTL_API, PTL_CLI) = ['api', 'cli']
- (PTL_COUNTER, PTL_FILTER) = [0, 1]
- PTL_STR_TO_OP = {
- '<': LT,
- '<=': LE,
- '=': EQ,
- '>=': GE,
- '>': GT,
- '!=': NE,
- ' set ': SET,
- ' unset ': UNSET,
- ' match ': MATCH,
- '~': MATCH_RE,
- '!': NOT
- }
- PTL_OP_TO_STR = {
- LT: '<',
- LE: '<=',
- EQ: '=',
- GE: '>=',
- GT: '>',
- SET: ' set ',
- NE: '!=',
- UNSET: ' unset ',
- MATCH: ' match ',
- MATCH_RE: '~',
- NOT: 'is not'
- }
- PTL_ATTROP_TO_STR = {PTL_AND: '&&', PTL_OR: '||'}
- (RESOURCES_AVAILABLE, RESOURCES_TOTAL) = [0, 1]
- EXPECT_MAP = {
- UNSET: 'Unset',
- SET: 'Set',
- EQ: 'Equal',
- NE: 'Not Equal',
- LT: 'Less Than',
- GT: 'Greater Than',
- LE: 'Less Equal Than',
- GE: 'Greater Equal Than',
- MATCH_RE: 'Matches regexp',
- MATCH: 'Matches',
- NOT: 'Not'
- }
- PBS_CMD_MAP = {
- MGR_CMD_CREATE: 'create',
- MGR_CMD_SET: 'set',
- MGR_CMD_DELETE: 'delete',
- MGR_CMD_UNSET: 'unset',
- MGR_CMD_IMPORT: 'import',
- MGR_CMD_EXPORT: 'export',
- MGR_CMD_LIST: 'list',
- }
- PBS_CMD_TO_OP = {
- MGR_CMD_SET: SET,
- MGR_CMD_UNSET: UNSET,
- MGR_CMD_DELETE: UNSET,
- MGR_CMD_CREATE: SET,
- }
- PBS_OBJ_MAP = {
- MGR_OBJ_NONE: 'none',
- SERVER: 'server',
- QUEUE: 'queue',
- JOB: 'job',
- NODE: 'node',
- RESV: 'reservation',
- RSC: 'resource',
- SCHED: 'sched',
- HOST: 'host',
- HOOK: 'hook',
- VNODE: 'node',
- PBS_HOOK: 'pbshook'
- }
- PTL_TRUE = ('1', 'true', 't', 'yes', 'y', 'enable', 'enabled', 'True', True)
- PTL_FALSE = ('0', 'false', 'f', 'no', 'n', 'disable', 'disabled', 'False',
- False)
- PTL_NONE = ('None', None)
- PTL_FORMULA = '__formula__'
- PTL_NOARG = '__noarg__'
- PTL_ALL = '__ALL__'
- CMD_ERROR_MAP = {
- 'alterjob': 'PbsAlterError',
- 'holdjob': 'PbsHoldError',
- 'sigjob': 'PbsSignalError',
- 'msgjob': 'PbsMessageError',
- 'rlsjob': 'PbsReleaseError',
- 'rerunjob': 'PbsRerunError',
- 'orderjob': 'PbsOrderError',
- 'runjob': 'PbsRunError',
- 'movejob': 'PbsMoveError',
- 'delete': 'PbsDeleteError',
- 'deljob': 'PbsDeljobError',
- 'delresv': 'PbsDelresvError',
- 'status': 'PbsStatusError',
- 'manager': 'PbsManagerError',
- 'submit': 'PbsSubmitError',
- 'terminate': 'PbsQtermError',
- 'alterresv': 'PbsResvAlterError'
- }
- class PtlConfig(object):
- """
- Holds configuration options
- The options can be stored in a file as well as in the OS environment
- variables.When set, the environment variables will override
- definitions in the file.By default, on Unix like systems, the file
- read is ``/etc/ptl.conf``, the environment variable ``PTL_CONF_FILE``
- can be used to set the path to the file to read.
- The format of the file is a series of ``<key> = <value>`` properties.
- A line that starts with a '#' is ignored and can be used for comments
- :param conf: Path to PTL configuration file
- :type conf: str or None
- """
- logger = logging.getLogger(__name__)
- def __init__(self, conf=None):
- self.options = {
- 'PTL_SUDO_CMD': 'sudo -H',
- 'PTL_RSH_CMD': 'ssh',
- 'PTL_CP_CMD': 'scp -p',
- 'PTL_EXPECT_MAX_ATTEMPTS': 60,
- 'PTL_EXPECT_INTERVAL': 0.5,
- 'PTL_UPDATE_ATTRIBUTES': True,
- }
- self.handlers = {
- 'PTL_SUDO_CMD': DshUtils.set_sudo_cmd,
- 'PTL_RSH_CMD': DshUtils.set_rsh_cmd,
- 'PTL_CP_CMD': DshUtils.set_copy_cmd,
- 'PTL_EXPECT_MAX_ATTEMPTS': Server.set_expect_max_attempts,
- 'PTL_EXPECT_INTERVAL': Server.set_expect_interval,
- 'PTL_UPDATE_ATTRIBUTES': Server.set_update_attributes
- }
- if conf is None:
- conf = os.environ.get('PTL_CONF_FILE', '/etc/ptl.conf')
- try:
- lines = open(conf).readlines()
- except IOError:
- lines = []
- for line in lines:
- line = line.strip()
- if (line.startswith('#') or (line == '')):
- continue
- try:
- k, v = line.split('=', 1)
- k = k.strip()
- v = v.strip()
- self.options[k] = v
- except:
- self.logger.error('Error parsing line ' + line)
- for k, v in self.options.items():
- if k in os.environ:
- v = os.environ[k]
- else:
- os.environ[k] = str(v)
- if k in self.handlers:
- self.handlers[k](v)
- class PtlException(Exception):
- """
- Generic errors raised by PTL operations.
- Sets a ``return value``, a ``return code``, and a ``message``
- A post function and associated positional and named arguments
- are available to perform any necessary cleanup.
- :param rv: Return value set for the error occured during PTL
- operation
- :type rv: int or None.
- :param rc: Return code set for the error occured during PTL
- operation
- :type rc: int or None.
- :param msg: Message set for the error occured during PTL operation
- :type msg: str or None.
- :param post: Execute given post callable function if not None.
- :type post: callable or None.
- :raises: PTL exceptions
- """
- def __init__(self, rv=None, rc=None, msg=None, post=None, *args, **kwargs):
- self.rv = rv
- self.rc = rc
- self.msg = msg
- if post is not None:
- post(*args, **kwargs)
- def __str__(self):
- return ('rc=' + str(self.rc) + ', rv=' + str(self.rv) +
- ', msg=' + str(self.msg))
- def __repr__(self):
- return (self.__class__.__name__ + '(rc=' + str(self.rc) + ', rv=' +
- str(self.rv) + ', msg=' + str(self.msg) + ')')
- class PtlFailureException(AssertionError):
- """
- Generic failure exception raised by PTL operations.
- Sets a ``return value``, a ``return code``, and a ``message``
- A post function and associated positional and named arguments
- are available to perform any necessary cleanup.
- :param rv: Return value set for the failure occured during PTL
- operation
- :type rv: int or None.
- :param rc: Return code set for the failure occured during PTL
- operation
- :type rc: int or None.
- :param msg: Message set for the failure occured during PTL operation
- :type msg: str or None.
- :param post: Execute given post callable function if not None.
- :type post: callable or None.
- :raises: PTL exceptions
- """
- def __init__(self, rv=None, rc=None, msg=None, post=None, *args, **kwargs):
- self.rv = rv
- self.rc = rc
- self.msg = msg
- if post is not None:
- post(*args, **kwargs)
- def __str__(self):
- return ('rc=' + str(self.rc) + ', rv=' + str(self.rv) +
- ', msg=' + str(self.msg))
- def __repr__(self):
- return (self.__class__.__name__ + '(rc=' + str(self.rc) + ', rv=' +
- str(self.rv) + ', msg=' + str(self.msg) + ')')
- class PbsServiceError(PtlException):
- pass
- class PbsConnectError(PtlException):
- pass
- class PbsStatusError(PtlException):
- pass
- class PbsSubmitError(PtlException):
- pass
- class PbsManagerError(PtlException):
- pass
- class PbsDeljobError(PtlException):
- pass
- class PbsDelresvError(PtlException):
- pass
- class PbsDeleteError(PtlException):
- pass
- class PbsRunError(PtlException):
- pass
- class PbsSignalError(PtlException):
- pass
- class PbsMessageError(PtlException):
- pass
- class PbsHoldError(PtlException):
- pass
- class PbsReleaseError(PtlException):
- pass
- class PbsOrderError(PtlException):
- pass
- class PbsRerunError(PtlException):
- pass
- class PbsMoveError(PtlException):
- pass
- class PbsAlterError(PtlException):
- pass
- class PbsResourceError(PtlException):
- pass
- class PbsSelectError(PtlException):
- pass
- class PbsSchedConfigError(PtlException):
- pass
- class PbsMomConfigError(PtlException):
- pass
- class PbsFairshareError(PtlException):
- pass
- class PbsQdisableError(PtlException):
- pass
- class PbsQenableError(PtlException):
- pass
- class PbsQstartError(PtlException):
- pass
- class PbsQstopError(PtlException):
- pass
- class PtlExpectError(PtlFailureException):
- pass
- class PbsInitServicesError(PtlException):
- pass
- class PbsQtermError(PtlException):
- pass
- class PtlLogMatchError(PtlFailureException):
- pass
- class PbsResvAlterError(PtlException):
- pass
- class PbsTypeSize(str):
- """
- Descriptor class for memory as a numeric entity.
- Units can be one of ``b``, ``kb``, ``mb``, ``gb``, ``tb``, ``pt``
- :param unit: The unit type associated to the memory value
- :type unit: str
- :param value: The numeric value of the memory
- :type value: int or None
- :raises: ValueError and TypeError
- """
- def __init__(self, value=None):
- if value is None:
- return
- if len(value) < 2:
- raise ValueError
- if value[-1:] in ('b', 'B') and value[:-1].isdigit():
- self.unit = 'b'
- self.value = int(int(value[:-1]) / 1024)
- return
- # lower() applied to ignore case
- unit = value[-2:].lower()
- self.value = value[:-2]
- if not self.value.isdigit():
- raise ValueError
- if unit == 'kb':
- self.value = int(self.value)
- elif unit == 'mb':
- self.value = int(self.value) * 1024
- elif unit == 'gb':
- self.value = int(self.value) * 1024 * 1024
- elif unit == 'tb':
- self.value = int(self.value) * 1024 * 1024 * 1024
- elif unit == 'pb':
- self.value = int(self.value) * 1024 * 1024 * 1024 * 1024
- else:
- raise TypeError
- self.unit = 'kb'
- def encode(self, value=None, valtype='kb', precision=1):
- """
- Encode numeric memory input in kilobytes to a string, including
- unit
- :param value: The numeric value of memory to encode
- :type value: int or None.
- :param valtype: The unit of the input value, defaults to kb
- :type valtype: str
- :param precision: Precision of the encoded value, defaults to 1
- :type precision: int
- :returns: Encoded memory in kb to string
- """
- if value is None:
- value = self.value
- if valtype == 'b':
- val = value
- elif valtype == 'kb':
- val = value * 1024
- elif valtype == 'mb':
- val = value * 1024 * 1024
- elif valtype == 'gb':
- val = value * 1024 * 1024 * 1024 * 1024
- elif valtype == 'tb':
- val = value * 1024 * 1024 * 1024 * 1024 * 1024
- elif valtype == 'pt':
- val = value * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
- m = (
- (1 << 50, 'pb'),
- (1 << 40, 'tb'),
- (1 << 30, 'gb'),
- (1 << 20, 'mb'),
- (1 << 10, 'kb'),
- (1, 'b')
- )
- for factor, suffix in m:
- if val >= factor:
- break
- return '%.*f%s' % (precision, float(val) / factor, suffix)
- def __cmp__(self, other):
- if self.value < other.value:
- return -1
- if self.value == other.value:
- return 0
- return 1
- def __lt__(self, other):
- if self.value < other.value:
- return True
- return False
- def __le__(self, other):
- if self.value <= other.value:
- return True
- return False
- def __gt__(self, other):
- if self.value > other.value:
- return True
- return False
- def __ge__(self, other):
- if self.value < other.value:
- return True
- return False
- def __eq__(self, other):
- if self.value == other.value:
- return True
- return False
- def __get__(self):
- return self.value
- def __add__(self, other):
- if isinstance(other, int):
- self.value += other
- else:
- self.value += other.value
- return self
- def __mul__(self, other):
- if isinstance(other, int):
- self.value *= other
- else:
- self.value *= other.value
- return self
- def __floordiv__(self, other):
- self.value /= other.value
- return self
- def __sub__(self, other):
- self.value -= other.value
- return self
- def __repr__(self):
- return self.__str__()
- def __str__(self):
- return self.encode(valtype=self.unit)
- class PbsTypeDuration(str):
- """
- Descriptor class for a duration represented as ``hours``,
- ``minutes``, and ``seconds``,in the form of ``[HH:][MM:]SS``
- :param as_seconds: HH:MM:SS represented in seconds
- :type as_seconds: int
- :param as_str: duration represented in HH:MM:SS
- :type as_str: str
- """
- def __init__(self, val):
- if isinstance(val, str):
- if ':' in val:
- s = val.split(':')
- l = len(s)
- if l > 3:
- raise ValueError
- hr = mn = sc = 0
- if l >= 2:
- sc = s[l - 1]
- mn = s[l - 2]
- if l == 3:
- hr = s[0]
- self.duration = int(hr) * 3600 + int(mn) * 60 + int(sc)
- elif val.isdigit():
- self.duration = int(val)
- elif isinstance(val, int) or isinstance(val, float):
- self.duration = val
- def __add__(self, other):
- self.duration += other.duration
- return self
- def __sub__(self, other):
- self.duration -= other.duration
- return self
- def __cmp__(self, other):
- if self.duration < other.duration:
- return -1
- if self.duration == other.duration:
- return 0
- return 1
- def __lt__(self, other):
- if self.duration < other.duration:
- return True
- return False
- def __le__(self, other):
- if self.duration <= other.duration:
- return True
- return False
- def __gt__(self, other):
- if self.duration > other.duration:
- return True
- return False
- def __ge__(self, other):
- if self.duration < other.duration:
- return True
- return False
- def __eq__(self, other):
- if self.duration == other.duration:
- return True
- return False
- def __get__(self):
- return self.as_str
- def __repr__(self):
- return self.__str__()
- def __int__(self):
- return int(self.duration)
- def __str__(self):
- return str(datetime.timedelta(seconds=self.duration))
- class PbsTypeArray(list):
- """
- Descriptor class for a PBS array list type, e.g. String array
- :param value: Array value to be passed
- :param sep: Separator for two array elements
- :type sep: str
- :returns: List
- """
- def __init__(self, value=None, sep=','):
- self.separator = sep
- self = list.__init__(self, value.split(sep))
- def __str__(self):
- return self.separator.join(self)
- class PbsTypeList(dict):
- """
- Descriptor class for a generic PBS list that are key/value pairs
- delimited
- :param value: List value to be passed
- :param sep: Separator for two key/value pair
- :type sep: str
- :param kvsep: Separator for key and value
- :type kvsep: str
- :returns: Dictionary
- """
- def __init__(self, value=None, sep=',', kvsep='='):
- self.kvsep = kvsep
- self.separator = sep
- d = {}
- as_list = map(lambda v: v.split(kvsep), value.split(sep))
- if as_list:
- for k, v in as_list:
- d[k] = v
- del as_list
- dict.__init__(self, d)
- def __str__(self):
- s = []
- for k, v in self.items():
- s += [str(k) + self.kvsep + str(v)]
- return self.separator.join(s)
- class PbsTypeLicenseCount(PbsTypeList):
- """
- Descriptor class for a PBS license_count attribute.
- It is a specialized list where key/values are ':' delimited, separated
- by a ' ' (space)
- :param value: PBS license_count attribute value
- :returns: Specialized list
- """
- def __init__(self, value=None):
- super(PbsTypeLicenseCount, self).__init__(value, sep=' ', kvsep=':')
- class PbsTypeVariableList(PbsTypeList):
- """
- Descriptor class for a PBS Variable_List attribute
- It is a specialized list where key/values are '=' delimited, separated
- by a ',' (space)
- :param value: PBS Variable_List attribute value
- :returns: Specialized list
- """
- def __init__(self, value=None):
- super(PbsTypeVariableList, self).__init__(value, sep=',', kvsep='=')
- class PbsTypeSelect(list):
- """
- Descriptor class for PBS select/schedselect specification.
- Select is of the form:
- ``<select> ::= <m>":"<chunk> | <select>"+"<select>``
- ``<m> ::= <digit> | <digit><m>``
- ``<chunk> ::= <resc_name>":"<resc_value> | <chunk>":"<chunk>``
- ``<m>`` is a multiplying factor for each chunk requested
- ``<chunk>`` are resource key/value pairs
- The type populates a list of single chunk of resource
- ``key/value`` pairs, the list can be walked by iterating over
- the type itself.
- :param num_chunks: The total number of chunks in the select
- :type num_chunk: int
- :param resources: A dictionary of all resource counts in the select
- :type resources: Dictionary
- """
- def __init__(self, s=None):
- if s is not None:
- self._as_str = s
- self.resources = {}
- self.num_chunks = 0
- nc = s.split('+')
- for chunk in nc:
- self._parse_chunk(chunk)
- def _parse_chunk(self, chunk):
- d = chunk.split(':')
- # number of chunks
- _num_chunks = int(d[0])
- self.num_chunks += _num_chunks
- r = {}
- for e in d[1:]:
- k, v = e.split('=')
- r[k] = v
- if 'mem' in k:
- try:
- v = PbsTypeSize(v).value
- except:
- # failed so we guessed wrong on the type
- pass
- if isinstance(v, int) or v.isdigit():
- if k not in self.resources:
- self.resources[k] = _num_chunks * int(v)
- else:
- self.resources[k] += _num_chunks * int(v)
- else:
- if k not in self.resources:
- self.resources[k] = v
- else:
- self.resources[k] = [self.resources[k], v]
- # explicitly expose the multiplying factor
- for _ in range(_num_chunks):
- self.append(r)
- def __add__(self, chunk=None):
- if chunk is None:
- return self
- self._parse_chunk(chunk)
- self._as_str = self._as_str + "+" + chunk
- return self
- def __repr__(self):
- return str(self)
- def __str__(self):
- return self._as_str
- class PbsTypeChunk(dict):
- """
- Descriptor class for a PBS chunk associated to a
- ``PbsTypeExecVnode``.This type of chunk corresponds to
- a node solution to a resource request,not to the select
- specification.
- ``chunk ::= <subchk> | <chunk>"+"<chunk>``
- ``subchk ::= <node>":"<resource>``
- ``resource ::= <key>":"<val> | <resource>":"<resource>``
- A chunk expresses a solution to a specific select-chunk
- request. If multiple chunks are needed to solve a single
- select-chunk, e.g., on a shared memory system, the chunk
- will be extended into virtual chunk,vchunk.
- :param vnode: the vnode name corresponding to the chunk
- :type vnode: str or None
- :param resources: the key value pair of resources in
- dictionary form
- :type resources: Dictionary or None
- :param vchunk: a list of virtual chunks needed to solve
- the select-chunk, vchunk is only set if more
- than one vchunk are required to solve the
- select-chunk
- :type vchunk: list
- """
- def __init__(self, vnode=None, resources=None, chunkstr=None):
- self.vnode = vnode
- if resources is not None:
- self.resources = resources
- else:
- self.resources = {}
- self.vchunk = []
- self.as_str = chunkstr
- self.__parse_chunk(chunkstr)
- def __parse_chunk(self, chunkstr=None):
- if chunkstr is None:
- return
- vchunks = chunkstr.split('+')
- if len(vchunks) == 1:
- entities = chunkstr.split(':')
- self.vnode = entities[0]
- if len(entities) > 1:
- for e in entities[1:]:
- (r, v) = e.split('=')
- self.resources[r] = v
- self[self.vnode] = self.resources
- else:
- for sc in vchunks:
- chk = PbsTypeChunk(chunkstr=sc)
- self.vchunk.append(chk)
- self[chk.vnode] = chk.resources
- def add(self, vnode, resources):
- """
- Add a chunk specificiation. If a chunk is already
- defined, add the chunk as a vchunk.
- :param vnode: The vnode to add
- :type vnode: str
- :param resources: The resources associated to the
- vnode
- :type resources: str
- :returns: Added chunk specification
- """
- if self.vnode == vnode:
- self.resources = dict(self.resources.items() + resources.items())
- return self
- elif len(self.vchunk) != 0:
- for chk in self.vchunk:
- if chk.vnode == vnode:
- chk.resources = dict(self.resources.items() +
- resources.items())
- return self
- chk = PbsTypeChunk(vnode, resources)
- self.vchunk.append(chk)
- return self
- def __repr__(self):
- return self.__str__()
- def __str__(self):
- _s = ["("]
- _s += [self.vnode, ":"]
- for resc_k, resc_v in self.resources.items():
- _s += [resc_k, "=", str(resc_v)]
- if self.vchunk:
- for _v in self.vchunk:
- _s += ["+", _v.vnode, ":"]
- for resc_k, resc_v in _v.resources.items():
- _s += [resc_k, "=", str(resc_v)]
- _s += [")"]
- return "".join(_s)
- class PbsTypeExecVnode(list):
- """
- Execvnode representation, expressed as a list of
- PbsTypeChunk
- :param vchunk: List of virtual chunks, only set when
- more than one vnode is allocated to a
- host satisfy a chunk requested
- :type vchunk: List
- :param num_chunks: The number of chunks satisfied by
- this execvnode
- :type num_chunks: int
- :param vnodes: List of vnode names allocated to the execvnode
- :type vnodes: List
- :param resource: method to return the amount of a named
- resource satisfied by this execvnode
- """
- def __init__(self, s=None):
- if s is None:
- return None
- self._as_str = s
- start = 0
- self.num_chunks = 0
- for c in range(len(s)):
- # must split on '+' between parens because '+' can occur within
- # paren for complex specs
- if s[c] == '(':
- start = c + 1
- if s[c] == ')':
- self.append(PbsTypeChunk(chunkstr=s[start:c]))
- self.num_chunks += 1
- def resource(self, name=None):
- """
- :param name: Name of the resource
- :type name: str or None
- """
- if name is None:
- return None
- _total = 0
- for _c in self:
- if _c.vchunk:
- for _v in _c.vchunk:
- if name in _v.resources:
- _total += int(_v.resources[name])
- if name in _c.resources:
- if name in _c.resources:
- _total += int(_c.resources[name])
- return _total
- @property
- def vnodes(self):
- vnodes = []
- for e in self:
- vnodes += [e.vnode]
- if e.vchunk:
- vnodes += map(lambda n: n.vnode, e.vchunk)
- return list(set(vnodes))
- def _str__(self):
- return self._as_str
- # below would be to verify that the converted type maps back correctly
- _s = []
- for _c in self:
- _s += [str(_c)]
- return "+".join(_s)
- class PbsTypeExecHost(str):
- """
- Descriptor class for exec_host attribute
- :param hosts: List of hosts in the exec_host. Each entry is
- a host info dictionary that maps the number of
- cpus and its task number
- :type hosts: List
- """
- def __init__(self, s=None):
- if s is None:
- return None
- self._as_str = s
- self.hosts = []
- hsts = s.split('+')
- for h in hsts:
- hi = {}
- ti = {}
- (host, task) = h.split('/',)
- d = task.split('*')
- if len(d) == 1:
- taskslot = d[0]
- ncpus = 1
- elif len(d) == 2:
- (taskslot, ncpus) = d
- else:
- (taskslot, ncpus) = (0, 1)
- ti['task'] = taskslot
- ti['ncpus'] = ncpus
- hi[host] = ti
- self.hosts.append(hi)
- def __repr__(self):
- return str(self.hosts)
- def __str__(self):
- return self._as_str
- class PbsTypeJobId(str):
- """
- Descriptor class for a Job identifier
- :param id: The numeric portion of a job identifier
- :type id: int
- :param server_name: The pbs server name
- :type server_name: str
- :param server_shortname: The first portion of a FQDN server
- name
- :type server_shortname: str
- """
- def __init__(self, value=None):
- if value is None:
- return
- self.value = value
- r = value.split('.', 1)
- if len(r) != 2:
- return
- self.id = int(r[0])
- self.server_name = r[1]
- self.server_shortname = r[1].split('.', 1)[0]
- def __str__(self):
- return str(self.value)
- class PbsUser(object):
- """
- The PbsUser type augments a PBS username to associate
- it to groups to which the user belongs
- :param name: The user name referenced
- :type name: str
- :param uid: uid of user
- :type uid: int or None
- :param groups: The list of PbsGroup objects the user
- belongs to
- :type groups: List or None
- """
- def __init__(self, name, uid=None, groups=None):
- self.name = name
- if uid is not None:
- self.uid = int(uid)
- else:
- self.uid = None
- self.home = None
- self.gid = None
- self.shell = None
- self.gecos = None
- try:
- _user = pwd.getpwnam(self.name)
- self.uid = _user.pw_uid
- self.home = _user.pw_dir
- self.gid = _user.pw_gid
- self.shell = _user.pw_shell
- self.gecos = _user.pw_gecos
- except:
- pass
- if groups is None:
- self.groups = []
- elif isinstance(groups, list):
- self.groups = groups
- else:
- self.groups = groups.split(",")
- for g in self.groups:
- if isinstance(g, str):
- self.groups.append(PbsGroup(g, users=[self]))
- elif self not in g.users:
- g.users.append(self)
- def __repr__(self):
- return str(self.name)
- def __str__(self):
- return self.__repr__()
- def __int__(self):
- return int(self.uid)
- class PbsGroup(object):
- """
- The PbsGroup type augments a PBS groupname to associate it
- to users to which the group belongs
- :param name: The group name referenced
- :type name: str
- :param gid: gid of group
- :type gid: int or None
- :param users: The list of PbsUser objects the group belongs to
- :type users: List or None
- """
- def __init__(self, name, gid=None, users=None):
- self.name = name
- if gid is not None:
- self.gid = int(gid)
- else:
- self.gid = None
- try:
- _group = grp.getgrnam(self.name)
- self.gid = _group.gr_gid
- except:
- pass
- if users is None:
- self.users = []
- elif isinstance(users, list):
- self.users = users
- else:
- self.users = users.split(",")
- for u in self.users:
- if isinstance(u, str):
- self.users.append(PbsUser(u, groups=[self]))
- elif self not in u.groups:
- u.groups.append(self)
- def __repr__(self):
- return str(self.name)
- def __str__(self):
- return self.__repr__()
- def __int__(self):
- return int(self.gid)
- class BatchUtils(object):
- """
- Utility class to create/convert/display various PBS
- data structures
- """
- legal = "\d\w:\+=\[\]~"
- chunks_tag = re.compile("(?P<chunk>\([\d\w:\+=\[\]~]\)[\+]?)")
- chunk_tag = re.compile("(?P<vnode>[\w\d\[\]]+):" +
- "(?P<resources>[\d\w:\+=\[\]~])+\)")
- array_tag = re.compile("(?P<jobid>[\d]+)\[(?P<subjobid>[0-9]*)\]*" +
- "[.]*[(?P<server>.*)]*")
- subjob_tag = re.compile("(?P<jobid>[\d]+)\[(?P<subjobid>[0-9]+)\]*" +
- "[.]*[(?P<server>.*)]*")
- pbsobjname_re = re.compile("^([\w\d][\d\w\s]*:?[\s]+)" +
- "*(?P<name>[\w@\.\d\[\]-]+)$")
- pbsobjattrval_re = re.compile(r"""
- [\s]*(?P<attribute>[\w\d\.-]+)
- [\s]*=[\s]*
- (?P<value>.*)
- [\s]*""",
- re.VERBOSE)
- dt_re = '(?P<dt_from>\d\d/\d\d/\d\d\d\d \d\d:\d\d)' + \
- '[\s]+' + \
- '(?P<dt_to>\d\d/\d\d/\d\d\d\d \d\d:\d\d)'
- dt_tag = re.compile(dt_re)
- hms_tag = re.compile('(?P<hr>\d\d):(?P<mn>\d\d):(?P<sc>\d\d)')
- lim_tag = re.compile("(?P<limtype>[a-z_]+)[\.]*(?P<resource>[\w\d-]*)"
- "=[\s]*\[(?P<entity_type>[ugpo]):"
- "(?P<entity_name>[\w\d-]+)"
- "=(?P<entity_value>[\d\w]+)\][\s]*")
- def __init__(self):
- self.logger = logging.getLogger(__name__)
- self.du = DshUtils()
- def list_to_attrl(self, l):
- """
- Convert a list to a PBS attribute list
- :param l: List to be converted
- :type l: List
- :returns: PBS attribute list
- """
- return self.list_to_attropl(l, None)
- def list_to_attropl(self, l, op=SET):
- """
- Convert a list to a PBS attribute operation list
- :param l: List to be converted
- :type l: List
- :returns: PBS attribute operation list
- """
- head = None
- prev = None
- for i in l:
- a = self.str_to_attropl(i, op)
- if prev is None:
- head = a
- else:
- prev.next = a
- prev = a
- if op is not None:
- a.op = op
- return head
- def str_to_attrl(self, s):
- """
- Convert a string to a PBS attribute list
- :param s: String to be converted
- :type s: str
- :returns: PBS attribute list
- """
- return self.str_to_attropl(s, None)
- def str_to_attropl(self, s, op=SET):
- """
- Convert a string to a PBS attribute operation list
- :param s: String to be converted
- :type s: str
- :returns: PBS attribute operation list
- """
- if op is not None:
- a = attropl()
- else:
- a = attrl()
- if '.' in s:
- (attribute, resource) = s.split('.')
- a.name = attribute
- a.resource = resource.strip()
- else:
- a.name = s
- a.value = ''
- a.next = None
- if op:
- a.op = op
- return a
- def dict_to_attrl(self, d={}):
- """
- Convert a dictionary to a PBS attribute list
- :param d: Dictionary to be converted
- :type d: Dictionary
- :returns: PBS attribute list
- """
- return self.dict_to_attropl(d, None)
- def dict_to_attropl(self, d={}, op=SET):
- """
- Convert a dictionary to a PBS attribute operation list
- :param d: Dictionary to be converted
- :type d: Dictionary
- :returns: PBS attribute operation list
- """
- if len(d.keys()) == 0:
- return None
- prev = None
- head = None
- for k, v in d.items():
- if isinstance(v, tuple):
- op = v[0]
- v = v[1]
- if op is not None:
- a = attropl()
- else:
- a = attrl()
- if '.' in k:
- (attribute, resource) = k.split('.')
- a.name = attribute
- a.resource = resource
- else:
- a.name = k
- a.value = str(v)
- if op is not None:
- a.op = op
- a.next = None
- if prev is None:
- head = a
- else:
- prev.next = a
- prev = a
- return head
- def convert_to_attrl(self, attrib):
- """
- Generic call to convert Python type to PBS attribute list
- :param attrib: Attributes to be converted
- :type attrib: List or tuple or dictionary or str
- :returns: PBS attribute list
- """
- return self.convert_to_attropl(attrib, None)
- def convert_to_attropl(self, attrib, cmd=MGR_CMD_SET, op=None):
- """
- Generic call to convert Python type to PBS attribute
- operation list
- :param attrib: Attributes to be converted
- :type attrib: List or tuple or dictionary or str
- :returns: PBS attribute operation list
- """
- if op is None:
- op = self.command_to_op(cmd)
- if isinstance(attrib, (list, tuple)):
- a = self.list_to_attropl(attrib, op)
- elif isinstance(attrib, (dict, OrderedDict)):
- a = self.dict_to_attropl(attrib, op)
- elif isinstance(attrib, str):
- a = self.str_to_attropl(attrib, op)
- else:
- a = None
- return a
- def command_to_op(self, cmd=None):
- """
- Map command to a ``SET`` or ``UNSET`` Operation. An unrecognized
- command will return SET. No command will return None.
- :param cmd: Command to be mapped
- :type cmd: str
- :returns: ``SET`` or ``UNSET`` operation for the command
- """
- if cmd is None:
- return None
- if cmd in (MGR_CMD_SET, MGR_CMD_EXPORT, MGR_CMD_IMPORT):
- return SET
- if cmd == MGR_CMD_UNSET:
- return UNSET
- return SET
- def display_attrl(self, a=None, writer=sys.stdout):
- """
- Display an attribute list using writer, defaults to sys.stdout
- :param a: Attributes
- :type a: List
- :returns: Displays attribute list
- """
- return self.display_attropl(a)
- def display_attropl(self, attropl=None, writer=sys.stdout):
- """
- Display an attribute operation list with writer, defaults to
- sys.stdout
- :param attropl: Attribute operation list
- :type attropl: List
- :returns: Displays an attribute operation list
- """
- attrs = attropl
- while attrs is not None:
- if attrs.resource:
- writer.write('\t' + attrs.name + '.' + attrs.resource + '= ' +
- attrs.value + '\n')
- else:
- writer.write('\t' + attrs.name + '= ' + attrs.value + '\n')
- attrs = attrs.next
- def display_dict(self, d, writer=sys.stdout):
- """
- Display a dictionary using writer, defaults to sys.stdout
- :param d: Dictionary
- :type d: Dictionary
- :returns: Displays a dictionary
- """
- if not d:
- return
- for k, v in d.items():
- writer.write(k + ': ' + v + '\n')
- def batch_status_to_dictlist(self, bs=None, attr_names=None, id=None):
- """
- Convert a batch status to a list of dictionaries.
- version 0.1a6 added this conversion as a typemap(out) as
- part of the swig wrapping itself so there are fewer uses
- for this function.Returns a list of dictionary
- representation of batch status
- :param bs: Batch status
- :param attr_names: Attribute names
- :returns: List of dictionaries
- """
- attr_time = (
- 'ctime', 'mtime', 'qtime', 'start', 'end', 'reserve_start',
- 'reserve_end', 'estimated.start_time')
- ret = []
- while bs:
- if id is not None and bs.name != id:
- bs = bs.next
- continue
- d = {}
- attrs = bs.attribs
- while attrs is not None:
- if attrs.resource:
- key = attrs.name + '.' + attrs.resource
- else:
- key = attrs.name
- if attr_names is not None:
- if key not in attr_names:
- attrs = attrs.next
- continue
- val = attrs.value
- if attrs.name in attr_time:
- val = self.convert_time(val)
- # for attributes that may occur multiple times (e.g., max_run)
- # append the value in a comma-separated representation
- if key in d:
- d[key] = d[key] + ',' + str(val)
- else:
- d[key] = str(val)
- attrs = attrs.next
- if len(d.keys()) > 0:
- ret.append(d)
- d['id'] = bs.name
- bs = bs.next
- return ret
- def display_batch_status(self, bs=None, attr_names=None,
- writer=sys.stdout):
- """
- Display a batch status using writer, defaults to sys.stdout
- :param bs: Batch status
- :param attr_name: Attribute name
- :type attr_name: str
- :returns: Displays batch status
- """
- if bs is None:
- return
- l = self.batch_status_to_dictlist(bs, attr_names)
- self.display_batch_status_as_dictlist(l, writer)
- def display_dictlist(self, l=[], writer=sys.stdout, fmt=None):
- """
- Display a list of dictionaries using writer, defaults to
- sys.stdout
- :param l: The list to display
- :type l: List
- :param writer: The stream on which to write
- :param fmt: An optional formatting string
- :type fmt: str or None
- :returns: Displays list of dictionaries
- """
- self.display_batch_status_as_dictlist(l, writer, fmt)
- def dictlist_to_file(self, l=[], filename=None, mode='w'):
- """
- write a dictlist to file
- :param l: Dictlist
- :type l: List
- :param filename: File to which dictlist need to be written
- :type filename: str
- :param mode: Mode of file
- :type mode: str
- :raises: Exception writing to file
- """
- if filename is None:
- self.logger.error('a filename is required')
- return
- d = os.path.dirname(filename)
- if d != '' and not os.path.isdir(d):
- os.makedirs(d)
- try:
- f = open(filename, mode)
- self.display_dictlist(l, f)
- f.close()
- except:
- self.logger.error('error writing to file ' + filename)
- raise
- def batch_status_as_dictlist_to_file(self, l=[], writer=sys.stdout):
- """
- Write a dictlist to file
- :param l: Dictlist
- :type l: List
- :raises: Exception writing to file
- """
- return self.dictlist_to_file(l, writer)
- def file_to_dictlist(self, file=None, attribs=None, id=None):
- """
- Convert a file to a batch dictlist format
- :param file: File to be converted
- :type file: str
- :param attribs: Attributes
- :returns: File converted to a batch dictlist format
- """
- if file is None:
- return []
- try:
- f = open(file, 'r')
- lines = f.readlines()
- f.close()
- except Exception, e:
- self.logger.error('error converting list of dictionaries to ' +
- 'file ' + str(e))
- return []
- return self.convert_to_dictlist(lines, attribs, id=id)
- def file_to_vnodedef(self, file=None):
- """
- Convert a file output of pbsnodes -av to a vnode
- definition format
- :param file: File to be converted
- :type sile: str
- :returns: Vnode definition format
- """
- if file is None:
- return None
- try:
- f = open(file, 'r')
- lines = f.readlines()
- f.close()
- except:
- self.logger.error('error converting nodes to vnode def')
- return None
- dl = self.convert_to_dictlist(lines)
- return self.dictlist_to_vnodedef(dl)
- def show(self, l=[], name=None, fmt=None):
- """
- Alias to display_dictlist with sys.stdout as writer
- :param name: if specified only show the object of
- that name
- :type name: str
- :param fmt: Optional formatting string, uses %n for
- object name, %a for attributes, for example
- a format of '%nE{\}nE{\}t%aE{\}n' will display
- objects with their name starting on the first
- column, a new line, and attributes indented by
- a tab followed by a new line at the end.
- :type fmt: str
- """
- if name:
- i = 0
- for obj in l:
- if obj['id'] == name:
- l = [l[i]]
- break
- i += 1
- self.display_dictlist(l, fmt=fmt)
- def get_objtype(self, d={}):
- """
- Get the type of a given object
- :param d: Dictionary
- :type d: Dictionary
- :Returns: Type of the object
- """
- if 'Job_Name' in d:
- return JOB
- elif 'queue_type' in d:
- return QUEUE
- elif 'Reserve_Name' in d:
- return RESV
- elif 'server_state' in d:
- return SERVER
- elif 'Mom' in d:
- return NODE
- elif 'event' in d:
- return HOOK
- elif 'type' in d:
- return RSC
- return None
- def display_batch_status_as_dictlist(self, l=[], writer=sys.stdout,
- fmt=None):
- """
- Display a batch status as a list of dictionaries
- using writer, defaults to sys.stdout
- :param l: List
- :type l: List
- :param fmt: - Optional format string
- :type fmt: str or None
- :returns: Displays batch status as a list of dictionaries
- """
- if l is None:
- return
- for d in l:
- self.display_batch_status_as_dict(d, writer, fmt)
- def batch_status_as_dict_to_str(self, d={}, fmt=None):
- """
- Return a string representation of a batch status dictionary
- :param d: Dictionary
- :type d: Dictionary
- :param fmt: Optional format string
- :type fmt: str or None
- :returns: String representation of a batch status dictionary
- """
- objtype = self.get_objtype(d)
- if fmt is not None:
- if '%1' in fmt:
- _d1 = fmt['%1']
- else:
- _d1 = '\n'
- if '%2' in fmt:
- _d2 = fmt['%2']
- else:
- _d2 = ' '
- if '%3' in fmt:
- _d3 = fmt['%3']
- else:
- _d3 = ' = '
- if '%4' in fmt:
- _d4 = fmt['%4']
- else:
- _d4 = '\n'
- if '%5' in fmt:
- _d5 = fmt['%5']
- else:
- _d5 = '\n'
- if '%6' in fmt:
- _d6 = fmt['%6']
- else:
- _d6 = ''
- else:
- _d1 = '\n'
- _d2 = ' '
- _d3 = ' = '
- _d4 = '\n'
- _d5 = '\n'
- _d6 = ''
- if objtype == JOB:
- _n = 'Job Id: ' + d['id'] + _d1
- elif objtype == QUEUE:
- _n = 'Queue: ' + d['id'] + _d1
- elif objtype == RESV:
- _n = 'Name: ' + d['id'] + _d1
- elif objtype == SERVER:
- _n = 'Server: ' + d['id'] + _d1
- elif objtype == RSC:
- _n = 'Resource: ' + d['id'] + _d1
- elif 'id' in d:
- _n = d['id'] + _d1
- del d['id']
- else:
- _n = ''
- _a = []
- for k, v in sorted(d.items()):
- if k == 'id':
- continue
- _a += [_d2 + k + _d3 + str(v)]
- return _n + _d4.join(_a) + _d5 + _d6
- def display_batch_status_as_dict(self, d={}, writer=sys.stdout, fmt=None):
- """
- Display a dictionary representation of a batch status
- using writer, defaults to sys.stdout
- :param d: Dictionary
- :type d: Dictionary
- :param fmt: Optional format string
- :param fmt: str
- :returns: Displays dictionary representation of a batch
- status
- """
- writer.write(self.batch_status_as_dict_to_str(d, fmt))
- def decode_dictlist(self, l=None, json=True):
- """
- decode a list of dictionaries
- :param l: List of dictionaries
- :type l: List
- :param json: The target of the decode is meant for ``JSON``
- formatting
- :returns: Decoded list of dictionaries
- """
- if l is None:
- return ''
- _js = []
- for d in l:
- _jdict = {}
- for k, v in d.items():
- if ',' in v:
- _jdict[k] = v.split(',')
- else:
- _jdict[k] = self.decode_value(v)
- _js.append(_jdict)
- return _js
- def convert_to_dictlist(self, l, attribs=None, mergelines=True, id=None):
- """
- Convert a list of records into a dictlist format.
- :param l: array of records to convert
- :type l: List
- :param mergelines: merge qstat broken lines into one
- :returns: Record list converted into dictlist format
- """
- if mergelines:
- lines = []
- for i in range(len(l)):
- if l[i].startswith('\t'):
- _e = len(lines) - 1
- lines[_e] = lines[_e].strip('\r\n\t') + \
- l[i].strip('\r\n\t')
- else:
- lines.append(l[i])
- else:
- lines = l
- objlist = []
- d = {}
- for l in lines:
- l = l.strip()
- m = self.pbsobjname_re.match(l)
- if m:
- if len(d.keys()) > 1:
- if id is None or (id is not None and d['id'] == id):
- objlist.append(d.copy())
- d = {}
- d['id'] = m.group('name')
- else:
- m = self.pbsobjattrval_re.match(l)
- if m:
- attr = m.group('attribute')
- if attribs is None or attr in attribs:
- if attr in d:
- d[attr] = d[attr] + "," + m.group('value')
- else:
- d[attr] = m.group('value')
- # add the last element
- if len(d.keys()) > 1:
- if id is None or (id is not None and d['id'] == id):
- objlist.append(d.copy())
- return objlist
- def convert_to_batch(self, l, mergelines=True):
- """
- Convert a list of records into a batch format.
- :param l: array of records to convert
- :type l: List
- :param mergelines: qstat breaks long lines over
- multiple lines, merge them\
- to one by default.
- :type mergelines: bool
- :returns: A linked list of batch status
- """
- if mergelines:
- lines = []
- for i in range(len(l)):
- if l[i].startswith('\t'):
- _e = len(lines) - 1
- lines[_e] = lines[_e].strip('\r\t') + \
- l[i].strip('\r\n')
- else:
- lines.append(l[i])
- else:
- lines = l
- head_bs = None
- prev_bs = None
- prev_attr = None
- for l in lines:
- l = l.strip()
- m = self.pbsobjname_re.match(l)
- if m:
- bs = batch_status()
- bs.name = m.group('name')
- bs.attribs = None
- bs.next = None
- if prev_bs:
- prev_bs.next = bs
- if head_bs is None:
- head_bs = bs
- prev_bs = bs
- prev_attr = None
- else:
- m = self.pbsobjattrval_re.match(l)
- if m:
- attr = attrl()
- attr.name = m.group('attribute')
- attr.value = m.group('value')
- attr.next = None
- if bs.attribs is None:
- bs.attribs = attr
- if prev_attr:
- prev_attr.next = attr
- prev_attr = attr
- return head_bs
- def file_to_batch(self, file=None):
- """
- Convert a file to batch format
- :param file: File to be converted
- :type file: str or None
- :returns: File converted into batch format
- """
- if file is None:
- return None
- try:
- f = open(file, 'r')
- l = f.readlines()
- f.close()
- except:
- self.logger.error('error converting file ' + file + ' to batch')
- return None
- return self.convert_to_batch(l)
- def batch_to_file(self, bs=None, file=None):
- """
- Write a batch object to file
- :param bs: Batch status
- :param file: File to which batch object is to be written
- :type file: str
- """
- if bs is None or file is None:
- return
- try:
- f = open(file, 'w')
- self.display_batch_status(bs, writer=f)
- f.close()
- except:
- self.logger.error('error converting batch status to file')
- def batch_to_vnodedef(self, bs):
- """
- :param bs: Batch status
- :returns: The vnode definition string representation
- of nodes batch_status
- """
- out = ["$configversion 2\n"]
- while bs is not None:
- attr = bs.attribs
- while attr is not None:
- if attr.name.startswith("resources_available") or \
- attr.name.startswith("sharing"):
- out += [bs.name + ": "]
- out += [attr.name + "=" + attr.value + "\n"]
- attr = attr.next
- bs = bs.next
- return "".join(out)
- def dictlist_to_vnodedef(self, dl=None):
- """
- :param dl: Dictionary list
- :type dl: List
- :returns: The vnode definition string representation
- of a dictlist
- """
- if dl is None:
- return ''
- out = ["$configversion 2\n"]
- for node in dl:
- for k, v in node.items():
- if (k.startswith("resources_available") or
- k.startswith("sharing") or
- k.startswith("provision_enable") or
- k.startswith("queue")):
- out += [node['id'] + ": "]
- # MoM dislikes empty values reported in vnode defs so
- # we substitute no value for an actual empty string
- if not v:
- v = '""'
- out += [k + "=" + str(v) + "\n"]
- return "".join(out)
- def objlist_to_dictlist(self, objlist=None):
- """
- Convert a list of PBS/PTL objects ``(e.g. Server/Job...)``
- into a dictionary list representation of the batch status
- :param objlist: List of ``PBS/PTL`` objects
- :type objlist: List
- :returns: Dictionary list representation of the batch status
- """
- if objlist is None:
- return None
- bsdlist = []
- for obj in objlist:
- newobj = self.obj_to_dict(obj)
- bsdlist.append(newobj)
- return bsdlist
- def obj_to_dict(self, obj):
- """
- Convert a PBS/PTL object (e.g. Server/Job...) into a
- dictionary format
- :param obj: ``PBS/PTL`` object
- :returns: Dictionary of ``PBS/PTL`` objects
- """
- newobj = dict(obj.attributes.items())
- newobj[id] = obj.name
- return newobj
- def parse_execvnode(self, s=None):
- """
- Parse an execvnode string into chunk objects
- :param s: Execvnode string
- :type s: str or None
- :returns: Chunk objects for parsed execvnode string
- """
- if s is None:
- return None
- chunks = []
- start = 0
- for c in range(len(s)):
- if s[c] == '(':
- start = c + 1
- if s[c] == ')':
- chunks.append(PbsTypeChunk(chunkstr=s[start:c]).info)
- return chunks
- def anupbs_exechost_numhosts(self, s=None):
- """
- :param s: Exechost string
- :type s: str or None
- """
- n = 0
- if '[' in s:
- eh = re.sub(r'.*\[(.*)\].*', r'\1', s)
- hosts = eh.split(',')
- for hid in hosts:
- elm = hid.split('-')
- if len(elm) == 2:
- n += int(elm[1]) - int(elm[0]) + 1
- else:
- n += 1
- else:
- n += 1
- return n
- def parse_exechost(self, s=None):
- """
- Parse an exechost string into a dictionary representation
- :param s: String to be parsed
- :type s: str or None
- :returns: Dictionary format of the exechost string
- """
- if s is None:
- return None
- hosts = []
- hsts = s.split('+')
- for h in hsts:
- hi = {}
- ti = {}
- (host, task) = h.split('/',)
- d = task.split('*')
- if len(d) == 1:
- taskslot = d[0]
- ncpus = 1
- elif len(d) == 2:
- (taskslot, ncpus) = d
- else:
- (taskslot, ncpus) = (0, 1)
- ti['task'] = taskslot
- ti['ncpus'] = ncpus
- hi[host] = ti
- hosts.append(hi)
- return hosts
- def parse_select(self, s=None):
- """
- Parse a ``select/schedselect`` string into a list
- of dictionaries.
- :param s: select/schedselect string
- :type s: str or None
- :returns: List of dictonaries
- """
- if s is None:
- return
- info = []
- chunks = s.split('+')
- for chunk in chunks:
- d = chunk.split(':')
- numchunks = int(d[0])
- resources = {}
- for e in d[1:]:
- k, v = e.split('=')
- resources[k] = v
- for _ in range(numchunks):
- info.append(resources)
- return info
- @classmethod
- def isfloat(cls, value):
- """
- returns true if value is a float or a string representation
- of a float returns false otherwise
- :param value: value to be checked
- :type value: str or int or float
- :returns: True or False
- """
- if isinstance(value, float):
- return True
- if isinstance(value, str):
- try:
- float(value)
- return True
- except ValueError:
- return False
- @classmethod
- def decode_value(cls, value):
- """
- Decode an attribute/resource value, if a value is
- made up of digits only then return the numeric value
- of it, if it is made of alphanumeric values only, return
- it as a string, if it is of type size, i.e., with a memory
- unit such as b,kb,mb,gb then return the converted size to
- kb without the unit
- :param value: attribute/resource value
- :type value: str or int
- :returns: int or float or string
- """
- if value is None or callable(value):
- return value
- if isinstance(value, (int, float)):
- return value
- if value.isdigit():
- return int(value)
- if value.isalpha() or value == '':
- return value
- if cls.isfloat(value):
- return float(value)
- if ':' in value:
- try:
- value = int(PbsTypeDuration(value))
- except ValueError:
- pass
- return value
- # TODO revisit: assume (this could be the wrong type, need a real
- # data model anyway) that the remaining is a memory expression
- try:
- value = PbsTypeSize(value)
- return value.value
- except ValueError:
- pass
- except TypeError:
- # if not then we pass to return the value as is
- pass
- return value
- def convert_time(self, val, fmt='%a %b %d %H:%M:%S %Y'):
- """
- Convert a date time format into number of seconds
- since epoch
- :param val: date time value
- :param fmt: date time format
- :type fmt: str
- :returns: seconds
- """
- # Tweak for NAS format that puts the number of seconds since epoch
- # in between
- if val.split()[0].isdigit():
- val = int(val.split()[0])
- elif not val.isdigit():
- val = time.strptime(val, fmt)
- val = int(time.mktime(val))
- return val
- def convert_duration(self, val):
- """
- Convert HH:MM:SS into number of seconds
- If a number is fed in, that number is returned
- If neither formatted data is fed in, returns 0
- :param val: duration value
- :type val: str
- :raises: Incorrect format error
- :returns: seconds
- """
- if val.isdigit():
- return int(val)
- hhmmss = val.split(':')
- if len(hhmmss) != 3:
- self.logger.error('Incorrect format, expected HH:MM:SS')
- return 0
- return int(hhmmss[0]) * 3600 + int(hhmmss[1]) * 60 + int(hhmmss[2])
- def convert_seconds_to_datetime(self, tm, fmt=None, seconds=True):
- """
- Convert time format to number of seconds since epoch
- :param tm: the time to convert
- :type tm: str
- :param fmt: optional format string. If used, the seconds
- parameter is ignored.Defaults to ``%Y%m%d%H%M``
- :type fmt: str or None
- :param seconds: if True, convert time with seconds
- granularity. Defaults to True.
- :type seconds: bool
- :returns: Number of seconds
- """
- if fmt is None:
- fmt = "%Y%m%d%H%M"
- if seconds:
- fmt += ".%S"
- return time.strftime(fmt, time.localtime(int(tm)))
- def convert_stime_to_seconds(self, st):
- """
- Convert a time to seconds, if we fail we return the
- original time
- :param st: Time to be converted
- :type st: str
- :returns: Number of seconds
- """
- try:
- ret = time.mktime(time.strptime(st, '%a %b %d %H:%M:%S %Y'))
- except:
- ret = st
- return ret
- def convert_dedtime(self, dtime):
- """
- Convert dedicated time string of form %m/%d/%Y %H:%M.
- :param dtime: A datetime string, as an entry in the
- dedicated_time file
- :type dtime: str
- :returns: A tuple of (from,to) of time since epoch
- """
- dtime_from = None
- dtime_to = None
- m = self.dt_tag.match(dtime.strip())
- if m:
- try:
- _f = "%m/%d/%Y %H:%M"
- dtime_from = self.convert_datetime_to_epoch(m.group('dt_from'),
- fmt=_f)
- dtime_to = self.convert_datetime_to_epoch(m.group('dt_to'),
- fmt=_f)
- except:
- self.logger.error('error converting dedicated time')
- return (dtime_from, dtime_to)
- def convert_datetime_to_epoch(self, mdyhms, fmt="%m/%d/%Y %H:%M:%S"):
- """
- Convert the date time to epoch
- :param mdyhms: date time
- :type mdyhms: str
- :param fmt: Format for date time
- :type fmt: str
- :returns: Epoch time
- """
- return int(time.mktime(time.strptime(mdyhms, fmt)))
- def compare_versions(self, v1, v2, op=None):
- """
- Compare v1 to v2 with respect to operation op
- :param v1: If not a looseversion, it gets converted
- to it
- :param v2: If not a looseversion, it gets converted
- to it
- :param op: An operation, one of ``LT``, ``LE``, ``EQ``,
- ``GE``, ``GT``
- :type op: str
- :returns: True or False
- """
- if op is None:
- self.logger.error('missing operator, one of LT,LE,EQ,GE,GT')
- return None
- if v1 is None or v2 is None:
- return False
- if isinstance(v1, str):
- v1 = LooseVersion(v1)
- if isinstance(v2, str):
- v2 = LooseVersion(v2)
- if op == GT:
- if v1 > v2:
- return True
- elif op == GE:
- if v1 >= v2:
- return True
- elif op == EQ:
- if v1 == v2:
- return True
- elif op == LT:
- if v1 < v2:
- return True
- elif op == LE:
- if v1 <= v2:
- return True
- return False
- def convert_arglist(self, attr):
- """
- strip the XML attributes from the argument list attribute
- :param attr: Argument list attributes
- :type attr: List
- :returns: Stripped XML attributes
- """
- xmls = "<jsdl-hpcpa:Argument>"
- xmle = "</jsdl-hpcpa:Argument>"
- nattr = attr.replace(xmls, " ")
- nattr = nattr.replace(xmle, " ")
- return nattr.strip()
- def convert_to_cli(self, attrs, op=None, hostname=None, dflt_conf=True,
- exclude_attrs=None):
- """
- Convert attributes into their CLI format counterpart. This
- method is far from complete, it grows as needs come by and
- could use a rewrite, especially going along with a rewrite
- of pbs_api_to_cli
- :param attrs: Attributes to convert
- :type attrs: List or str or dictionary
- :param op: The qualifier of the operation being performed,
- such as ``IFL_SUBMIT``, ``IFL_DELETE``,
- ``IFL_TERMINUTE``...
- :type op: str or None
- :param hostname: The name of the host on which to operate
- :type hostname: str or None
- :param dflt_conf: Whether we are using the default PBS
- configuration
- :type dflt_conf: bool
- :param exclude_attrs: Optional list of attributes to not
- convert
- :type exclude_attrs: List
- :returns: CLI format of attributes
- """
- ret = []
- if op == IFL_SUBMIT:
- executable = arglist = None
- elif op == IFL_DELETE:
- _c = []
- if isinstance(attrs, str):
- attrs = [attrs]
- if isinstance(attrs, list):
- for a in attrs:
- if 'force' in a:
- _c.append('-W')
- _c.append('force')
- if 'deletehist' in a:
- _c.append('-x')
- return _c
- elif op == IFL_TERMINATE:
- _c = []
- if attrs is None:
- _c = []
- elif isinstance(attrs, str):
- _c = ['-t', attrs]
- else:
- if ((attrs & SHUT_QUICK) == SHUT_QUICK):
- _c = ['-t', 'quick']
- if ((attrs & SHUT_IMMEDIATE) == SHUT_IMMEDIATE):
- _c = ['-t', 'immediate']
- if ((attrs & SHUT_DELAY) == SHUT_DELAY):
- _c = ['-t', 'delay']
- if ((attrs & SHUT_WHO_SCHED) == SHUT_WHO_SCHED):
- _c.append('-s')
- if ((attrs & SHUT_WHO_MOM) == SHUT_WHO_MOM):
- _c.append('-m')
- if ((attrs & SHUT_WHO_SECDRY) == SHUT_WHO_SECDRY):
- _c.append('-f')
- if ((attrs & SHUT_WHO_IDLESECDRY) == SHUT_WHO_IDLESECDRY):
- _c.append('-F')
- if ((attrs & SHUT_WHO_SECDONLY) == SHUT_WHO_SECDONLY):
- _c.append('-i')
- return _c
- if attrs is None or len(attrs) == 0:
- return ret
- # if a list, convert to a dictionary to fall into a single processing
- # of the attributes
- if (isinstance(attrs, list) and len(attrs) > 0 and
- not isinstance(attrs[0], tuple)):
- tmp_attrs = {}
- for each_attr in attrs:
- tmp_attrs[each_attr] = ''
- del attrs
- attrs = tmp_attrs
- del tmp_attrs
- if isinstance(attrs, (dict, OrderedDict)):
- attrs = attrs.items()
- for a, v in attrs:
- if exclude_attrs is not None and a in exclude_attrs:
- continue
- if op == IFL_SUBMIT:
- if a == ATTR_executable:
- executable = v
- continue
- if a == ATTR_Arglist:
- if v is not None:
- arglist = self.convert_arglist(v)
- if len(arglist) == 0:
- return []
- continue
- if isinstance(v, list):
- v = ','.join(v)
- # when issuing remote commands, escape spaces in attribute values
- if (((hostname is not None) and
- (not self.du.is_localhost(hostname))) or
- (not dflt_conf)):
- if ' ' in str(v):
- v = '"' + v + '"'
- if '.' in a:
- (attribute, resource) = a.split('.')
- ret.append('-' + api_to_cli[attribute])
- rv = resource
- if v is not None:
- rv += '=' + str(v)
- ret.append(rv)
- else:
- try:
- val = api_to_cli[a]
- except KeyError:
- self.logger.error('error retrieving key ' + str(a))
- # for unknown or junk options
- ret.append(a)
- if v is not None:
- ret.append(str(v))
- continue
- # on a remote job submit append the remote server name
- # to the queue name
- if ((op == IFL_SUBMIT) and (hostname is not None)):
- if ((not self.du.is_localhost(hostname)) and
- (val == 'q') and (v is not None) and
- ('@' not in v) and (v != '')):
- v += '@' + hostname
- val = '-' + val
- if '=' in val:
- if v is not None:
- ret.append(val + str(v))
- else:
- ret.append(val)
- else:
- ret.append(val)
- if v is not None:
- ret.append(str(v))
- # Executable and argument list must come last in a job submission
- if ((op == IFL_SUBMIT) and (executable is not None)):
- ret.append('--')
- ret.append(executable)
- if arglist is not None:
- ret.append(arglist)
- return ret
- def filter_batch_status(self, bs, attrib):
- """
- Filter out elements that don't have the attributes requested
- This is needed to adapt to the fact that requesting a
- resource attribute returns all ``'<resource-name>.*'``
- attributes so we need to ensure that the specific resource
- requested is present in the stat'ed object.
- This is needed especially when calling expect with an op=NE
- because we need to filter on objects that have exactly
- the attributes requested
- :param bs: Batch status
- :param attrib: Requested attributes
- :type attrib: str or dictionary
- :returns: Filtered batch status
- """
- if isinstance(attrib, dict):
- keys = attrib.keys()
- elif isinstance(attrib, str):
- keys = attrib.split(',')
- else:
- keys = attrib
- if keys:
- del_indices = []
- for idx in range(len(bs)):
- for k in bs[idx].keys():
- if '.' not in k:
- continue
- if k != 'id' and k not in keys:
- del bs[idx][k]
- # if no matching resources, remove the object
- if len(bs[idx]) == 1:
- del_indices.append(idx)
- for i in sorted(del_indices, reverse=True):
- del bs[i]
- return bs
- def convert_attributes_by_op(self, attributes, setattrs=False):
- """
- Convert attributes by operator, i.e. convert an attribute
- of the form
- ``<attr_name><op><value>`` (e.g. resources_available.ncpus>4)
- to
- ``<attr_name>: (<op>, <value>)``
- (e.g. resources_available.ncpus: (GT, 4))
- :param attributes: the attributes to convert
- :type attributes: List
- :param setattrs: if True, set the attributes with no operator
- as (SET, '')
- :type setattrs: bool
- :returns: Converted attributes by operator
- """
- # the order of operator matters because they are used to search by
- # regex so the longer strings to search must come first
- operators = ('<=', '>=', '!=', '=', '>', '<', '~')
- d = {}
- for attr in attributes:
- found = False
- for op in operators:
- if op in attr:
- a = attr.split(op)
- d[a[0]] = (PTL_STR_TO_OP[op], a[1])
- found = True
- break
- if not found and setattrs:
- d[attr] = (SET, '')
- return d
- def operator_in_attribute(self, attrib):
- """
- Returns True if an operator string is present in an
- attribute name
- :param attrib: Attribute name
- :type attrib: str
- :returns: True or False
- """
- operators = PTL_STR_TO_OP.keys()
- for a in attrib:
- for op in operators:
- if op in a:
- return True
- return False
- def list_resources(self, objtype=None, objs=[]):
- """
- Lists the resources
- :param objtype: Type of the object
- :type objtype: str
- :param objs: Object list
- :type objs: List
- :returns: List of resources
- """
- if objtype in (VNODE, NODE, SERVER, QUEUE, SCHED):
- prefix = 'resources_available.'
- elif objtype in (JOB, RESV):
- prefix = 'Resource_List.'
- else:
- return
- resources = []
- for o in objs:
- for a in o.keys():
- if a.startswith(prefix):
- res = a.replace(prefix, '')
- if res not in resources:
- resources.append(res)
- return resources
- def compare(self, obj1, obj2, showdiff=False):
- """
- Compare two objects.
- :param showdiff: whether to print the specific differences,
- defaults to False
- :type showdiff: bool
- :returns: 0 if objects are identical and non zero otherwise
- """
- if not showdiff:
- ret = cmp(obj1, obj2)
- if ret != 0:
- self.logger.info('objects differ')
- return ret
- if not isinstance(obj1, type(obj2)):
- self.logger.error('objects are of different type')
- return 1
- if isinstance(obj1, list):
- if len(obj1) != len(obj2):
- self.logger.info(
- 'comparing ' + str(
- obj1) + ' and ' + str(
- obj2))
- self.logger.info('objects are of different lengths')
- return
- for i in range(len(obj1)):
- self.compare(obj1[i], obj2[i], showdiff=showdiff)
- return
- if isinstance(obj1, dict):
- self.logger.info('comparing ' + str(obj1) + ' and ' + str(obj2))
- onlyobj1 = []
- diffobjs = []
- onlyobj2 = []
- for k1, v1 in obj1.items():
- if k1 not in obj2:
- onlyobj1.append(k1 + '=' + str(v1))
- if k1 in obj2 and obj2[k1] != v1:
- diffobjs.append(
- k1 + '=' + str(v1) + ' vs ' + k1 + '=' + str(obj2[k1]))
- for k2, v2 in obj2.items():
- if k2 not in obj1:
- onlyobj2.append(k2 + '=' + str(v2))
- if len(onlyobj1) > 0:
- self.logger.info("only in first object: " + " ".join(onlyobj1))
- if len(onlyobj2) > 0:
- self.logger.info(
- "only in second object: " + " ".join(onlyobj2))
- if len(diffobjs) > 0:
- self.logger.info("diff between objects: " + " ".join(diffobjs))
- if len(onlyobj1) == len(onlyobj2) == len(diffobjs) == 0:
- self.logger.info("objects are identical")
- return 0
- return 1
- @classmethod
- def random_str(cls, length=1, prefix=''):
- """
- Generates the random string
- :param length: Length of the string
- :type length: int
- :param prefix: Prefix of the string
- :type prefix: str
- :returns: Random string
- """
- r = [random.choice(string.letters) for _ in range(length)]
- r = ''.join([prefix] + r)
- if hasattr(cls, '__uniq_rstr'):
- while r in cls.__uniq_rstr:
- r = [random.choice(string.letters) for _ in range(length)]
- r = ''.join([prefix] + r)
- cls.__uniq_rstr.append(r)
- else:
- cls.__uniq_rstr = [r]
- return r
- def _make_template_formula(self, formula):
- """
- Create a template of the formula
- :param formula: Formula for which template is to be created
- :type formula: str
- :returns: Template
- """
- tformula = []
- skip = False
- for c in formula:
- if not skip and c.isalpha():
- tformula.append('$')
- skip = True
- if c in ('+', '-', '/', ' ', '*', '%'):
- skip = False
- tformula.append(c)
- return "".join(tformula)
- def update_attributes_list(self, obj):
- """
- Updates the attribute list
- :param obj: Objects
- :returns: Updated attribute list
- """
- if not hasattr(obj, 'attributes'):
- return
- if not hasattr(obj, 'Resource_List'):
- setattr(obj, 'Resource_List', {})
- for attr, val in obj.attributes.items():
- if attr.startswith('Resource_List.'):
- (_, resource) = attr.split('.')
- obj.Resource_List[resource] = val
- def parse_fgc_limit(self, limstr=None):
- """
- Parse an ``FGC`` limit entry, of the form:
- ``<limtype>[.<resource>]=\[<entity_type>:<entity_name>
- =<entity_value>\]``
- :param limstr: FGC limit string
- :type limstr: str or None
- :returns: Parsed FGC string in given format
- """
- m = self.lim_tag.match(limstr)
- if m:
- _v = str(self.decode_value(m.group('entity_value')))
- return (m.group('limtype'), m.group('resource'),
- m.group('entity_type'), m.group('entity_name'), _v)
- return None
- def is_job_array(self, jobid):
- """
- If a job array return True, otherwise return False
- :param jobid: PBS jobid
- :returns: True or False
- """
- if self.array_tag.match(jobid):
- return True
- return False
- def is_subjob(self, jobid):
- """
- If a subjob of a job array, return the subjob id
- otherwise return False
- :param jobid: PBS job id
- :type jobid: str
- :returns: True or False
- """
- m = self.subjob_tag.match(jobid)
- if m:
- return m.group('subjobid')
- return False
- class PbsTypeFGCLimit(object):
- """
- FGC limit entry, of the form:
- ``<limtype>[.<resource>]=\[<entity_type>:<entity_name>=
- <entity_value>\]``
- :param attr: FGC limit attribute
- :type attr: str
- :param value: Value of attribute
- :type value: int
- :returns: FGC limit entry of given format
- """
- fgc_attr_pat = re.compile("(?P<ltype>[a-z_]+)[\.]*(?P<resource>[\w\d-]*)")
- fgc_val_pat = re.compile("[\s]*\[(?P<etype>[ugpo]):(?P<ename>[\w\d-]+)"
- "=(?P<eval>[\d]+)\][\s]*")
- utils = BatchUtils()
- def __init__(self, attr, val):
- self.attr = attr
- self.val = val
- a = self.fgc_attr_pat.match(attr)
- if a:
- self.limit_type = a.group('ltype')
- self.resource_name = a.group('resource')
- else:
- self.limit_type = None
- self.resource_name = None
- v = self.fgc_val_pat.match(val)
- if v:
- self.lim_value = self.utils.decode_value(v.group('eval'))
- self.entity_type = v.group('etype')
- self.entity_name = v.group('ename')
- else:
- self.lim_value = None
- self.entity_type = None
- self.entity_name = None
- def __val__(self):
- return ('[' + str(self.entity_type) + ':' +
- str(self.entity_name) + '=' + str(self.lim_value) + ']')
- def __str__(self):
- return (self.attr + ' = ' + self.__val__())
- class PbsBatchStatus(list):
- """
- Wrapper class for Batch Status object
- Converts a batch status (as dictlist) into a list of
- PbsBatchObjects
- :param bs: Batch status
- :type bs: List or dictionary
- :returns: List of PBS batch objects
- """
- def __init__(self, bs):
- if not isinstance(bs, (list, dict)):
- raise TypeError("Expected a list or dictionary")
- if isinstance(bs, dict):
- self.append(PbsBatchObject(bs))
- else:
- for b in bs:
- self.append(PbsBatchObject(b))
- def __str__(self):
- rv = []
- for l in self.__bs:
- rv += [self.__bu.batch_status_as_dict_to_str(l)]
- return "\n".join(rv)
- class PbsBatchObject(list):
- def __init__(self, bs):
- self.set_batch_status(bs)
- def set_batch_status(self, bs):
- """
- Sets the batch status
- :param bs: Batch status
- """
- if 'id' in bs:
- self.name = bs['id']
- for k, v in bs.items():
- self.append(PbsAttribute(k, v))
- class PbsAttribute(object):
- """
- Descriptor class for PBS attribute
- :param name: PBS attribute name
- :type name: str
- :param value: Value for the attribute
- :type value: str or int or float
- """
- utils = BatchUtils()
- def __init__(self, name=None, value=None):
- self.set_name(name)
- self.set_value(value)
- def set_name(self, name):
- """
- Set PBS attribute name
- :param name: PBS attribute
- :type name: str
- """
- self.name = name
- if name is not None and '.' in name:
- self.is_resource = True
- self.resource_type, self.resource_name = self.name.split('.')
- else:
- self.is_resource = False
- self.resource_type = self.resource_name = None
- def set_value(self, value):
- """
- Set PBS attribute value
- :param value: Value of PBS attribute
- :type value: str or int or float
- """
- self.value = value
- if isinstance(value, (int, float)) or str(value).isdigit():
- self.is_consumable = True
- else:
- self.is_consumable = False
- def obfuscate_name(self, a=None):
- """
- Obfuscate PBS attribute name
- """
- if a is not None:
- on = a
- else:
- on = self.utils.random_str(len(self.name))
- self.decoded_name = self.name
- if self.is_resource:
- self.set_name(self.resource_name + '.' + on)
- def obfuscate_value(self, v=None):
- """
- Obfuscate PBS attribute value
- """
- if not self.is_consuable:
- self.decoded_value = self.value
- return
- if v is not None:
- ov = v
- else:
- ov = self.utils.random_str(len(self.value))
- self.decoded_value = self.value
- self.set_value(ov)
- class Entity(object):
- """
- Abstract representation of a PBS consumer that has an
- external relationship to the PBS system. For example, a
- user associated to an OS identifier (uid) maps to a PBS
- user entity.
- Entities may be subject to policies, such as limits, consume
- a certain amount of resource and/or fairshare usage.
- :param etype: Entity type
- :type etype: str or None
- :param name: Entity name
- :type name: str or None
- """
- def __init__(self, etype=None, name=None):
- self.type = etype
- self.name = name
- self.limits = []
- self.resource_usage = {}
- self.fairshare_usage = 0
- def set_limit(self, limit=None):
- """
- :param limit: Limit to be set
- :type limit: str or None
- """
- for l in self.limits:
- if str(limit) == str(l):
- return
- self.limits.append(limit)
- def set_resource_usage(self, container=None, resource=None, usage=None):
- """
- Set the resource type
- :param resource: PBS resource
- :type resource: str or None
- :param usage: Resource usage value
- :type usage: str or None
- """
- if self.type:
- if container in self.resource_usage:
- if self.resource_usage[self.type]:
- if resource in self.resource_usage[container]:
- self.resource_usage[container][resource] += usage
- else:
- self.resource_usage[container][resource] = usage
- else:
- self.resource_usage[container] = {resource: usage}
- def set_fairshare_usage(self, usage=0):
- """
- Set fairshare usage
- :param usage: Fairshare usage value
- :type usage: int
- """
- self.fairshare_usage += usage
- def __repr__(self):
- return self.__str__()
- def __str__(self):
- return str(self.limits) + ' ' + str(self.resource_usage) + ' ' + \
- str(self.fairshare_usage)
- class Policy(object):
- """
- Abstract PBS policy. Can be one of ``limits``,
- ``access control``, ``scheduling policy``, etc...this
- class does not currently support any operations
- """
- def __init__(self):
- pass
- class Limit(Policy):
- """
- Representation of a PBS limit
- Limits apply to containers, are of a certain type
- (e.g., max_run_res.ncpus) associated to a given resource
- (e.g., resource), on a given entity (e.g.,user Bob) and
- have a certain value.
- :param limit_type: Type of the limit
- :type limit_type: str or None
- :param resource: PBS resource
- :type resource: str or None
- :param entity_obj: Entity object
- :param value: Limit value
- :type value: int
- """
- def __init__(self, limit_type=None, resource=None,
- entity_obj=None, value=None, container=None,
- container_id=None):
- self.set_container(container, container_id)
- self.soft_limit = False
- self.hard_limit = False
- self.set_limit_type(limit_type)
- self.set_resource(resource)
- self.set_value(value)
- self.entity = entity_obj
- def set_container(self, container, container_id):
- """
- Set the container
- :param container: Container which is to be set
- :type container: str
- :param container_id: Container id
- """
- self.container = container
- self.container_id = container_id
- def set_limit_type(self, t):
- """
- Set the limit type
- :param t: Limit type
- :type t: str
- """
- self.limit_type = t
- if '_soft' in t:
- self.soft_limit = True
- else:
- self.hard_limit = True
- def set_resource(self, resource):
- """
- Set the resource
- :param resource: resource value to set
- :type resource: str
- """
- self.resource = resource
- def set_value(self, value):
- """
- Set the resource value
- :param value: Resource value
- :type value: str
- """
- self.value = value
- def __eq__(self, value):
- if str(self) == str(value):
- return True
- return False
- def __str__(self):
- return self.__repr__()
- def __repr__(self):
- l = [self.container_id, self.limit_type, self.resource, '[',
- self.entity.type, ':', self.entity.name, '=', self.value, ']']
- return " ".join(l)
- class ExpectActions(object):
- """
- List of action handlers to run when Server's expect
- function does not get the expected result
- :param action: Action to run
- :type action: str
- :param level: Logging level
- """
- actions = {}
- def __init__(self, action=None, level=logging.INFO):
- self.logger = logging.getLogger(__name__)
- self.add_action(action, level=level)
- def add_action(self, action=None, hostname=None, level=logging.INFO):
- """
- Add an action
- :param action: Action to add
- :param hostname: Machine hostname
- :type hostname: str
- :param level: Logging level
- """
- if action is not None and action.name is not None and\
- action.name not in self.actions:
- self.actions[action.name] = action
- msg = ['expect action: added action ' + action.name]
- if hostname:
- msg += [' to server ' + hostname]
- if level >= logging.INFO:
- self.logger.info("".join(msg))
- else:
- self.logger.debug("".join(msg))
- def has_action(self, name):
- """
- check whether action exists or not
- :param name: Name of action
- :type name: str
- """
- if name in self.actions:
- return True
- return False
- def get_action(self, name):
- """
- Get an action if exists
- :param name: Name of action
- :type name: str
- """
- if name in self.actions:
- return self.actions[name]
- return None
- def list_actions(self, level=logging.INFO):
- """
- List an actions
- :param level: Logging level
- """
- if level >= logging.INFO:
- self.logger.info(self.get_all_cations)
- else:
- self.logger.debug(self.get_all_cations)
- def get_all_actions(self):
- """
- Get all the action
- """
- return self.actions.values()
- def get_actions_by_type(self, atype=None):
- """
- Get an action by type
- :param atype: Action type
- :type atype: str
- """
- if atype is None:
- return None
- ret_actions = []
- for action in self.actions.values():
- if action.type is not None and action.type == atype:
- ret_actions.append(action)
- return ret_actions
- def _control_action(self, action=None, name=None, enable=None):
- if action:
- action.enabled = False
- name = action.name
- elif name is not None:
- if name == 'ALL':
- for a in self.actions:
- a.enabled = enable
- else:
- a = self.get_action(name)
- a.enabled = False
- else:
- return
- if enable:
- msg = 'enabled'
- else:
- msg = 'disabled'
- self.logger.info('expect action: ' + name + ' ' + msg)
- def disable_action(self, action=None, name=None):
- """
- Disable an action
- """
- self._control_action(action, name, enable=False)
- def enable_action(self, action=None, name=None):
- """
- Enable an action
- """
- self._control_action(action, name, enable=True)
- def disable_all_actions(self):
- """
- Disable all actions
- """
- for a in self.actions.values():
- a.enabled = False
- def enable_all_actions(self):
- """
- Enable all actions
- """
- for a in self.actions.values():
- a.enabled = True
- class ExpectAction(object):
- """
- Action function to run when Server's expect function does
- not get the expected result
- :param atype: Action type
- :type atype: str
- """
- def __init__(self, name=None, enabled=True, atype=None, action=None,
- level=logging.INFO):
- self.logger = logging.getLogger(__name__)
- self.set_name(name, level=level)
- self.set_enabled(enabled)
- self.set_type(atype)
- self.set_action(action)
- def set_name(self, name, level=logging.INFO):
- """
- Set the actione name
- :param name: Action name
- :type name: str
- """
- if level >= logging.INFO:
- self.logger.info('expect action: created new action ' + name)
- else:
- self.logger.debug('expect action: created new action ' + name)
- self.name = name
- def set_enabled(self, enabled):
- self.enabled = enabled
- def set_type(self, atype):
- self.type = atype
- def set_action(self, action):
- self.action = action
- class PbsTypeAttribute(dict):
- """
- Experimental. This is a placeholder object that will be used
- in the future to map attribute information and circumvent
- the error-pron dynamic type detection that is currently done
- using ``decode_value()``
- """
- def __getitem__(self, name):
- return BatchUtils.decode_value(super(PbsTypeAttribute,
- self).__getitem__(name))
- class PBSObject(object):
- """
- Generic PBS Object encapsulating attributes and defaults
- :param name: The name associated to the object
- :type name: str
- :param attrs: Dictionary of attributes to set on object
- :type attrs: Dictionary
- :param defaults: Dictionary of default attributes. Setting
- this will override any other object's default
- :type defaults: Dictionary
- """
- utils = BatchUtils()
- platform = sys.platform
- def __init__(self, name, attrs={}, defaults={}):
- self.attributes = OrderedDict()
- self.name = name
- self.dflt_attributes = defaults
- self.attropl = None
- self.custom_attrs = OrderedDict()
- self.ctime = int(time.time())
- self.set_attributes(attrs)
- def set_attributes(self, a={}):
- """
- set attributes and custom attributes on this object.
- custom attributes are used when converting attributes
- to CLI
- :param a: Attribute dictionary
- :type a: Dictionary
- """
- if isinstance(a, list):
- a = OrderedDict(a)
- self.attributes = OrderedDict(self.dflt_attributes.items() +
- self.attributes.items() + a.items())
- self.custom_attrs = OrderedDict(self.custom_attrs.items() +
- a.items())
- def unset_attributes(self, attrl=[]):
- """
- Unset attributes from object's attributes and custom
- attributes
- :param attrl: Attribute list
- :type attrl: List
- """
- for attr in attrl:
- if attr in self.attributes:
- del self.attributes[attr]
- if attr in self.custom_attrs:
- del self.custom_attrs[attr]
- def __str__(self):
- """
- Return a string representation of this PBSObject
- """
- if self.name is None:
- return ""
- s = []
- if isinstance(self, Job):
- s += ["Job Id: " + self.name + "\n"]
- elif isinstance(self, Queue):
- s += ["Queue: " + self.name + "\n"]
- elif isinstance(self, Server):
- s += ["Server: " + self.hostname + "\n"]
- elif isinstance(self, Reservation):
- s += ["Name: " + "\n"]
- else:
- s += [self.name + "\n"]
- for k, v in self.attributes.items():
- s += [" " + k + " = " + str(v) + "\n"]
- return "".join(s)
- def __repr__(self):
- return str(self.attributes)
- class PBSService(PBSObject):
- """
- Generic PBS service object to hold properties of PBS daemons
- :param name: The name associated to the object
- :type name: str or None
- :param attrs: Dictionary of attributes to set on object
- :type attrs: Dictionary
- :param defaults: Dictionary of default attributes. Setting
- this will override any other object's default
- :type defaults: Dictionary
- :param pbsconf_file: Optional path to the pbs configuration
- file
- :type pbsconf_file: str or None
- :param diagmap: A dictionary of PBS objects (node,server,etc)
- to mapped files from PBS diag directory
- :type diagmap: Dictionary
- :param diag: path to PBS diag directory
- (This will overrides diagmap)
- :type diag: str or None
- """
- du = DshUtils()
- pu = ProcUtils()
- def __init__(self, name=None, attrs={}, defaults={}, pbsconf_file=None,
- diagmap={}, diag=None):
- if name is None:
- self.hostname = socket.gethostname()
- else:
- self.hostname = name
- if diag:
- self.diagmap = self._load_from_diag(diag)
- self.has_diag = True
- self.diag = diag
- elif len(diagmap) > 0:
- self.diagmap = diagmap
- self.diag = None
- self.has_diag = True
- else:
- self.diagmap = {}
- self.diag = None
- self.has_diag = False
- if not self.has_diag:
- try:
- self.fqdn = socket.gethostbyaddr(self.hostname)[0]
- if self.hostname != self.fqdn:
- self.logger.info('FQDN name ' + self.fqdn + ' differs '
- 'from name provided ' + self.hostname)
- self.hostname = self.fqdn
- except:
- pass
- else:
- self.fqdn = self.hostname
- self.shortname = self.hostname.split('.')[0]
- self.platform = self.du.get_platform()
- self.logutils = None
- self.logfile = None
- self.acctlogfile = None
- self.pid = None
- self.pbs_conf = {}
- self.pbs_env = {}
- self._is_local = True
- self.launcher = None
- PBSObject.__init__(self, name, attrs, defaults)
- if not self.has_diag:
- if not self.du.is_localhost(self.hostname):
- self._is_local = False
- if pbsconf_file is None and not self.has_diag:
- self.pbs_conf_file = self.du.get_pbs_conf_file(name)
- else:
- self.pbs_conf_file = pbsconf_file
- if self.pbs_conf_file == '/etc/pbs.conf':
- self.default_pbs_conf = True
- elif (('PBS_CONF_FILE' not in os.environ) or
- (os.environ['PBS_CONF_FILE'] != self.pbs_conf_file)):
- self.default_pbs_conf = False
- else:
- self.default_pbs_conf = True
- # default pbs_server_name to hostname, it will get set again once the
- # config file is processed
- self.pbs_server_name = self.hostname
- # If diag is given then bypass parsing pbs.conf
- if self.has_diag:
- if diag is None:
- t = 'pbs_diag_%s' % (time.strftime("%y%m%d_%H%M%S"))
- self.diag = os.path.join(self.du.get_tempdir(), t)
- self.pbs_conf['PBS_HOME'] = self.diag
- self.pbs_conf['PBS_EXEC'] = self.diag
- self.pbs_conf['PBS_SERVER'] = self.hostname
- m = re.match('.*pbs_diag_(?P<datetime>\d{6,6}_\d{6,6}).*',
- self.diag)
- if m:
- tm = time.strptime(m.group('datetime'), "%y%m%d_%H%M%S")
- self.ctime = int(time.mktime(tm))
- else:
- self.pbs_conf = self.du.parse_pbs_config(self.hostname,
- self.pbs_conf_file)
- if self.pbs_conf is None or len(self.pbs_conf) == 0:
- self.pbs_conf = {'PBS_HOME': "", 'PBS_EXEC': ""}
- else:
- ef = os.path.join(self.pbs_conf['PBS_HOME'], 'pbs_environment')
- self.pbs_env = self.du.parse_pbs_environment(self.hostname, ef)
- self.pbs_server_name = self.du.get_pbs_server_name(
- self.pbs_conf)
- self.init_logfile_path(self.pbs_conf)
- def _load_from_diag(self, diag):
- diagmap = {}
- diagmap[SERVER] = os.path.join(diag, 'qstat_Bf.out')
- diagmap[VNODE] = os.path.join(diag, 'pbsnodes_va.out')
- diagmap[QUEUE] = os.path.join(diag, 'qstat_Qf.out')
- diagmap[JOB] = os.path.join(diag, 'qstat_tf.out')
- if not os.path.isfile(diagmap[JOB]):
- diagmap[JOB] = os.path.join(diag, 'qstat_f.out')
- diagmap[RESV] = os.path.join(diag, 'pbs_rstat_f.out')
- diagmap[SCHED] = os.path.join(diag, 'qmgr_psched.out')
- diagmap[HOOK] = []
- if (os.path.isdir(os.path.join(diag, 'server_priv')) and
- os.path.isdir(os.path.join(diag, 'server_priv', 'hooks'))):
- _ld = os.listdir(os.path.join(diag, 'server_priv', 'hooks'))
- for f in _ld:
- if f.endswith('.HK'):
- diagmap[HOOK].append(
- os.path.join(diag, 'server_priv', 'hooks', f))
- # Format of qmgr_psched.out differs from Batch Status, we transform
- # it to go through the common batch status parsing routines
- if os.path.isfile(diagmap[SCHED]):
- f = open(os.path.join(diag, 'ptl_qstat_Sched.out'), 'w')
- lines = open(diagmap[SCHED])
- f.write("Sched \n")
- for l in lines:
- recs = l.split()
- f.write("".join(recs[2:5]) + "\n")
- f.close()
- diagmap[SCHED] = os.path.join(diag, 'ptl_qstat_Sched.out')
- else:
- diagmap[SCHED] = None
- return diagmap
- def init_logfile_path(self, conf=None):
- """
- Initialize path to log files for this service
- :param conf: PBS conf file parameters
- :type conf: Dictionary
- """
- elmt = self._instance_to_logpath(self)
- if elmt is None:
- return
- if conf is not None and 'PBS_HOME' in conf:
- tm = time.strftime("%Y%m%d", time.localtime())
- self.logfile = os.path.join(conf['PBS_HOME'], elmt, tm)
- self.acctlogfile = os.path.join(conf['PBS_HOME'], 'server_priv',
- 'accounting', tm)
- def _instance_to_logpath(self, inst):
- """
- returns the log path associated to this service
- """
- if isinstance(inst, Scheduler):
- logval = 'sched_logs'
- elif isinstance(inst, Server):
- logval = 'server_logs'
- elif isinstance(inst, MoM):
- logval = 'mom_logs'
- elif isinstance(inst, Comm):
- logval = 'comm_logs'
- else:
- logval = None
- return logval
- def _instance_to_cmd(self, inst):
- """
- returns the command associated to this service
- """
- if isinstance(inst, Scheduler):
- cmd = 'pbs_sched'
- elif isinstance(inst, Server):
- cmd = 'pbs_server'
- elif isinstance(inst, MoM):
- cmd = 'pbs_mom'
- elif isinstance(inst, Comm):
- cmd = 'pbs_comm'
- else:
- cmd = None
- return cmd
- def _instance_to_servicename(self, inst):
- """
- return the service name associated to the instance. One of
- ``server, scheduler, or mom.``
- """
- if isinstance(inst, Scheduler):
- nm = 'scheduler'
- elif isinstance(inst, Server):
- nm = 'server'
- elif isinstance(inst, MoM):
- nm = 'mom'
- elif isinstance(inst, Comm):
- nm = 'comm'
- else:
- nm = ''
- return nm
- def _instance_to_privpath(self, inst):
- """
- returns the path to priv associated to this service
- """
- if isinstance(inst, Scheduler):
- priv = 'sched_priv'
- elif isinstance(inst, Server):
- priv = 'server_priv'
- elif isinstance(inst, MoM):
- priv = 'mom_priv'
- elif isinstance(inst, Comm):
- priv = 'server_priv'
- else:
- priv = None
- return priv
- def _instance_to_lock(self, inst):
- """
- returns the path to lock file associated to this service
- """
- if isinstance(inst, Scheduler):
- lock = 'sched.lock'
- elif isinstance(inst, Server):
- lock = 'server.lock'
- elif isinstance(inst, MoM):
- lock = 'mom.lock'
- elif isinstance(inst, Comm):
- lock = 'comm.lock'
- else:
- lock = None
- return lock
- def set_launcher(self, execargs=None):
- self.launcher = execargs
- def _isUp(self, inst):
- """
- returns True if service is up and False otherwise
- """
- live_pids = self._all_instance_pids(inst)
- pid = self._get_pid(inst)
- if live_pids is not None and pid in live_pids:
- return True
- return False
- def _signal(self, sig, inst=None, procname=None):
- """
- Send signal ``sig`` to service. sig is the signal name
- as it would be sent to the program kill, e.g. -HUP.
- Return the ``out/err/rc`` from the command run to send
- the signal. See DshUtils.run_cmd
- :param inst: Instance
- :type inst: str
- :param procname: Process name
- :type procname: str or None
- """
- pid = None
- if inst is not None:
- if inst.pid is not None:
- pid = inst.pid
- else:
- pid = self._get_pid(inst)
- if procname is not None:
- pi = self.pu.get_proc_info(self.hostname, procname)
- if pi is not None and pi.values() and pi.values()[0]:
- for _p in pi.values()[0]:
- ret = self.du.run_cmd(self.hostname, ['kill', sig, _p.pid],
- sudo=True)
- return ret
- if pid is None:
- return {'rc': 0, 'err': '', 'out': 'no pid to signal'}
- return self.du.run_cmd(self.hostname, ['kill', sig, pid], sudo=True)
- def _all_instance_pids(self, inst):
- """
- Return a list of all ``PIDS`` that match the
- instance name or None.
- """
- cmd = self._instance_to_cmd(inst)
- self.pu.get_proc_info(self.hostname, ".*" + cmd + ".*",
- regexp=True)
- _procs = self.pu.processes.values()
- if _procs:
- _pids = []
- for _p in _procs:
- _pids.extend(map(lambda x: x.pid, _p))
- return _pids
- return None
- def _get_pid(self, inst):
- """
- Get the ``PID`` associated to this instance.
- Implementation note, the pid is read from the
- daemon's lock file.
- This is different than _all_instance_pids in that
- the PID of the last running instance can be retrieved
- with ``_get_pid`` but not with ``_all_instance_pids``
- """
- priv = self._instance_to_privpath(inst)
- lock = self._instance_to_lock(inst)
- if isinstance(inst, Scheduler) and 'sched_priv' in inst.attributes:
- path = os.path.join(inst.attributes['sched_priv'], lock)
- else:
- path = os.path.join(self.pbs_conf['PBS_HOME'], priv, lock)
- rv = self.du.cat(self.hostname, path, sudo=True, logerr=False)
- if ((rv['rc'] == 0) and (len(rv['out']) > 0)):
- self.pid = rv['out'][0].strip()
- else:
- self.pid = None
- return self.pid
- def _update_pid(self, inst):
- """
- update pid of given inst
- :param inst: inst to update pid
- :type inst: object
- """
- for i in range(30):
- live_pids = self._all_instance_pids(inst)
- inst.pid = self._get_pid(inst)
- if live_pids is not None and inst.pid in live_pids:
- return
- time.sleep(1)
- inst.pid = None
- def _start(self, inst=None, args=None, cmd_map=None, launcher=None):
- """
- Generic service startup
- :param inst: The instance to act upon
- :type inst: str
- :param args: Optional command-line arguments
- :type args: List
- :param cmd_map: Optional dictionary of command line
- options to configuration variables
- :type cmd_map: Dictionary
- :param launcher: Optional utility to invoke the launch
- of the service. This option only takes
- effect on ``Unix/Linux``. The option can
- be a string or a list.Options may be passed
- to the launcher, for example to start a
- service through the valgrind utility
- redirecting to a log file,launcher could be
- set to e.g.
- ``['valgrind', '--log-file=/tmp/vlgrd.out']``
- or ``'valgrind --log-file=/tmp/vlgrd.out'``
- """
- if launcher is None and self.launcher is not None:
- launcher = self.launcher
- app = self._instance_to_cmd(inst)
- if app is None:
- return
- _m = ['service: starting', app]
- if args is not None:
- _m += ['with args: ']
- _m += args
- as_script = False
- wait_on = True
- if launcher is not None:
- if isinstance(launcher, str):
- launcher = launcher.split()
- if app == 'pbs_server':
- # running the pbs server through valgrind requires a bit of
- # a dance because the pbs_server binary is pbs_server.bin
- # and to run it requires being able to find libraries, so
- # LD_LIBRARY_PATH is set and pbs_server.bin is run as a
- # script
- pexec = inst.pbs_conf['PBS_EXEC']
- ldlib = ['LD_LIBRARY_PATH=' +
- os.path.join(pexec, 'lib') + ':' +
- os.path.join(pexec, 'pgsql', 'lib')]
- app = 'pbs_server.bin'
- else:
- ldlib = []
- cmd = ldlib + launcher
- as_script = True
- wait_on = False
- else:
- cmd = []
- cmd += [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', app)]
- if args is not None:
- cmd += args
- if not self.default_pbs_conf:
- cmd = ['PBS_CONF_FILE=' + inst.pbs_conf_file] + cmd
- as_script = True
- if cmd_map is not None:
- conf_cmd = self.du.map_pbs_conf_to_cmd(cmd_map,
- pconf=self.pbs_conf)
- cmd.extend(conf_cmd)
- _m += conf_cmd
- self.logger.info(" ".join(_m))
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True,
- as_script=as_script, wait_on_script=wait_on,
- level=logging.INFOCLI, logerr=False)
- if ret['rc'] != 0:
- raise PbsServiceError(rv=False, rc=ret['rc'], msg=ret['err'])
- ret_msg = True
- if ret['err']:
- ret_msg = ret['err']
- self._update_pid(inst)
- if inst.pid is None:
- raise PbsServiceError(rv=False, rc=-1, msg="Could not find PID")
- return ret_msg
- def _stop(self, sig='-TERM', inst=None):
- if inst is None:
- return True
- self._signal(sig, inst)
- pid = self._get_pid(inst)
- chk_pid = self._all_instance_pids(inst)
- if pid is None or chk_pid is None:
- return True
- num_seconds = 0
- while (chk_pid is not None) and (str(pid) in chk_pid):
- if num_seconds > 60:
- m = (self.logprefix + 'could not stop service ' +
- self._instance_to_servicename(inst))
- raise PbsServiceError(rv=False, rc=-1, msg=m)
- time.sleep(1)
- num_seconds += 1
- chk_pid = self._all_instance_pids(inst)
- inst.pid = None
- return True
- def initialise_service(self):
- """
- Purpose of this method is to override and initialise
- the service
- """
- def log_lines(self, logtype, id=None, n=50, tail=True, starttime=None,
- endtime=None):
- """
- Return the last ``<n>`` lines of a PBS log file, which
- can be one of ``server``, ``scheduler``, ``MoM``, or
- ``tracejob``
- :param logtype: The entity requested, an instance of a
- Scheduler, Server or MoM object, or the
- string 'tracejob' for tracejob
- :type logtype: str or object
- :param id: The id of the object to trace. Only used for
- tracejob
- :param n: One of 'ALL' of the number of lines to
- process/display, defaults to 50.
- :type n: str or int
- :param tail: if True, parse log from the end to the start,
- otherwise parse from the start to the end.
- Defaults to True.
- :type tail: bool
- :param day: Optional day in ``YYYMMDD`` format. Defaults
- to current day
- :type day: int
- :param starttime: date timestamp to start matching
- :param endtime: date timestamp to end matching
- :returns: Last ``<n>`` lines of logfile for ``Server``,
- ``Scheduler``, ``MoM or tracejob``
- """
- logval = None
- lines = []
- sudo = False
- if endtime is None:
- endtime = int(time.time())
- if starttime is None:
- starttime = self.ctime
- try:
- if logtype == 'tracejob':
- if id is None:
- return None
- cmd = [os.path.join(
- self.pbs_conf['PBS_EXEC'],
- 'bin',
- 'tracejob')]
- cmd += [str(id)]
- lines = self.du.run_cmd(self.hostname, cmd)['out']
- if n != 'ALL':
- lines = lines[-n:]
- else:
- daystart = time.strftime("%Y%m%d", time.localtime(starttime))
- dayend = time.strftime("%Y%m%d", time.localtime(endtime))
- firstday_obj = datetime.datetime.strptime(daystart, '%Y%m%d')
- lastday_obj = datetime.datetime.strptime(dayend, '%Y%m%d')
- if logtype == 'accounting':
- logdir = os.path.join(self.pbs_conf['PBS_HOME'],
- 'server_priv', 'accounting')
- sudo = True
- elif (isinstance(self, Scheduler) and
- 'sched_log' in self.attributes):
- # if setup is multi-sched then get logdir from
- # its attributes
- logdir = self.attributes['sched_log']
- else:
- logval = self._instance_to_logpath(logtype)
- if logval is None:
- m = 'Invalid logtype'
- raise PtlLogMatchError(rv=False, rc=-1, msg=m)
- logdir = os.path.join(self.pbs_conf['PBS_HOME'], logval)
- while firstday_obj <= lastday_obj:
- day = firstday_obj.strftime("%Y%m%d")
- filename = os.path.join(logdir, day)
- if n == 'ALL':
- if self._is_local and not sudo:
- with open(filename) as f:
- day_lines = f.readlines()
- else:
- day_lines = self.du.cat(
- self.hostname, filename, sudo=sudo,
- level=logging.DEBUG2)['out']
- elif self._is_local and not sudo:
- if tail:
- futils = FileUtils(filename, FILE_TAIL)
- else:
- futils = FileUtils(filename)
- day_lines = futils.next(n)
- else:
- if tail:
- cmd = ['/usr/bin/tail']
- else:
- cmd = ['/usr/bin/head']
- cmd += ['-n']
- cmd += [str(n), filename]
- day_lines = self.du.run_cmd(
- self.hostname, cmd, sudo=sudo,
- level=logging.DEBUG2)['out']
- lines.extend(day_lines)
- firstday_obj = firstday_obj + datetime.timedelta(days=1)
- if n == 'ALL':
- continue
- n = n - len(day_lines)
- if n <= 0:
- break
- except:
- self.logger.error('error in log_lines ')
- traceback.print_exc()
- return None
- return lines
- def _log_match(self, logtype, msg, id=None, n=50, tail=True,
- allmatch=False, regexp=False, max_attempts=None,
- interval=None, starttime=None, endtime=None,
- level=logging.INFO, existence=True):
- """
- Match given ``msg`` in given ``n`` lines of log file
- :param logtype: The entity requested, an instance of a
- Scheduler, Server, or MoM object, or the
- strings 'tracejob' for tracejob or
- 'accounting' for accounting logs.
- :type logtype: object
- :param msg: log message to match, can be regex also when
- ``regexp`` is True
- :type msg: str
- :param id: The id of the object to trace. Only used for
- tracejob
- :type id: str
- :param n: 'ALL' or the number of lines to search through,
- defaults to 50
- :type n: str or int
- :param tail: If true (default), starts from the end of
- the file
- :type tail: bool
- :param allmatch: If True all matching lines out of then
- parsed are returned as a list. Defaults
- to False
- :type allmatch: bool
- :param regexp: If true msg is a Python regular expression.
- Defaults to False
- :type regexp: bool
- :param max_attempts: the number of attempts to make to find
- a matching entry
- :type max_attempts: int
- :param interval: the interval between attempts
- :type interval: int
- :param starttime: If set ignore matches that occur before
- specified time
- :type starttime: int
- :param endtime: If set ignore matches that occur after
- specified time
- :type endtime: int
- :param level: The logging level, defaults to INFO
- :type level: int
- :param existence: If True (default), check for existence of
- given msg, else check for non-existence of
- given msg.
- :type existence: bool
- :return: (x,y) where x is the matching line
- number and y the line itself. If allmatch is True,
- a list of tuples is returned.
- :rtype: tuple
- :raises PtlLogMatchError:
- When ``existence`` is True and given
- ``msg`` is not found in ``n`` line
- Or
- When ``existence`` is False and given
- ``msg`` found in ``n`` line.
- .. note:: The matching line number is relative to the record
- number, not the absolute line number in the file.
- """
- try:
- from ptl.utils.pbs_logutils import PBSLogUtils
- except:
- _msg = 'error loading ptl.utils.pbs_logutils'
- raise PtlLogMatchError(rc=1, rv=False, msg=_msg)
- if self.logutils is None:
- self.logutils = PBSLogUtils()
- if max_attempts is None:
- max_attempts = 60
- if interval is None:
- interval = 0.5
- rv = (None, None)
- attempt = 1
- lines = None
- name = self._instance_to_servicename(logtype)
- infomsg = (name + ' ' + self.shortname +
- ' log match: searching for "' + msg + '"')
- if regexp:
- infomsg += ' - using regular expression '
- if allmatch:
- infomsg += ' - on all matches '
- attemptmsg = ' - No match'
- while attempt <= max_attempts:
- if attempt > 1:
- attemptmsg = ' - attempt ' + str(attempt)
- lines = self.log_lines(logtype, id, n=n, tail=tail,
- starttime=starttime, endtime=endtime)
- rv = self.logutils.match_msg(lines, msg, allmatch=allmatch,
- regexp=regexp, starttime=starttime,
- endtime=endtime)
- if rv:
- self.logger.log(level, infomsg + '... OK')
- break
- else:
- if ((starttime is not None or endtime is not None) and
- n != 'ALL'):
- if attempt > max_attempts:
- # We will do one last attempt to match in case the
- # number of lines that were provided did not capture
- # the start or end time of interest
- max_attempts += 1
- n = 'ALL'
- self.logger.log(level, infomsg + attemptmsg)
- attempt += 1
- time.sleep(interval)
- try:
- # Depending on whether the hostname is local or remote and whether
- # sudo privileges were required, lines returned by log_lines can be
- # an open file descriptor, we close here but ignore errors in case
- # any were raised for all irrelevant cases
- lines.close()
- except:
- pass
- if (rv is None and existence) or (rv is not None and not existence):
- _msg = infomsg + attemptmsg
- raise PtlLogMatchError(rc=1, rv=False, msg=_msg)
- return rv
- def accounting_match(self, msg, id=None, n=50, tail=True,
- allmatch=False, regexp=False, max_attempts=None,
- interval=None, starttime=None, endtime=None,
- level=logging.INFO, existence=True):
- """
- Match given ``msg`` in given ``n`` lines of accounting log
- :param msg: log message to match, can be regex also when
- ``regexp`` is True
- :type msg: str
- :param id: The id of the object to trace. Only used for
- tracejob
- :type id: str
- :param n: 'ALL' or the number of lines to search through,
- defaults to 50
- :type n: str or int
- :param tail: If true (default), starts from the end of
- the file
- :type tail: bool
- :param allmatch: If True all matching lines out of then
- parsed are returned as a list. Defaults
- to False
- :type allmatch: bool
- :param regexp: If true msg is a Python regular expression.
- Defaults to False
- :type regexp: bool
- :param max_attempts: the number of attempts to make to find
- a matching entry
- :type max_attempts: int
- :param interval: the interval between attempts
- :type interval: int
- :param starttime: If set ignore matches that occur before
- specified time
- :type starttime: int
- :param endtime: If set ignore matches that occur after
- specified time
- :type endtime: int
- :param level: The logging level, defaults to INFO
- :type level: int
- :param existence: If True (default), check for existence of
- given msg, else check for non-existence of
- given msg.
- :type existence: bool
- :return: (x,y) where x is the matching line
- number and y the line itself. If allmatch is True,
- a list of tuples is returned.
- :rtype: tuple
- :raises PtlLogMatchError:
- When ``existence`` is True and given
- ``msg`` is not found in ``n`` line
- Or
- When ``existence`` is False and given
- ``msg`` found in ``n`` line.
- .. note:: The matching line number is relative to the record
- number, not the absolute line number in the file.
- """
- return self._log_match('accounting', msg, id, n, tail, allmatch,
- regexp, max_attempts, interval, starttime,
- endtime, level, existence)
- def tracejob_match(self, msg, id=None, n=50, tail=True,
- allmatch=False, regexp=False, max_attempts=None,
- interval=None, starttime=None, endtime=None,
- level=logging.INFO, existence=True):
- """
- Match given ``msg`` in given ``n`` lines of tracejob log
- :param msg: log message to match, can be regex also when
- ``regexp`` is True
- :type msg: str
- :param id: The id of the object to trace.
- :type id: str
- :param n: 'ALL' or the number of lines to search through,
- defaults to 50
- :type n: str or int
- :param tail: If true (default), starts from the end of
- the file
- :type tail: bool
- :param allmatch: If True all matching lines out of then
- parsed are returned as a list. Defaults
- to False
- :type allmatch: bool
- :param regexp: If true msg is a Python regular expression.
- Defaults to False
- :type regexp: bool
- :param max_attempts: the number of attempts to make to find
- a matching entry
- :type max_attempts: int
- :param interval: the interval between attempts
- :type interval: int
- :param starttime: If set ignore matches that occur before
- specified time
- :type starttime: int
- :param endtime: If set ignore matches that occur after
- specified time
- :type endtime: int
- :param level: The logging level, defaults to INFO
- :type level: int
- :param existence: If True (default), check for existence of
- given msg, else check for non-existence of
- given msg.
- :type existence: bool
- :return: (x,y) where x is the matching line
- number and y the line itself. If allmatch is True,
- a list of tuples is returned.
- :rtype: tuple
- :raises PtlLogMatchError:
- When ``existence`` is True and given
- ``msg`` is not found in ``n`` line
- Or
- When ``existence`` is False and given
- ``msg`` found in ``n`` line.
- .. note:: The matching line number is relative to the record
- number, not the absolute line number in the file.
- """
- return self._log_match('tracejob', msg, id, n, tail, allmatch,
- regexp, max_attempts, interval, starttime,
- endtime, level, existence)
- def _save_config_file(self, dict_conf, fname):
- ret = self.du.cat(self.hostname, fname, sudo=True)
- if ret['rc'] == 0:
- dict_conf[fname] = ret['out']
- else:
- self.logger.error('error saving configuration ' + fname)
- def _load_configuration(self, infile, objtype=None):
- """
- Load configuration as was saved in infile
- :param infile: the file in which configuration
- was saved
- :type infile: str
- :param objtype: the object type to load configuration
- for, one of server, scheduler, mom or
- if None, load all objects in infile
- """
- if os.path.isfile(infile):
- conf = {}
- f = open(infile, 'r')
- # load all objects from the Pickled file
- while True:
- try:
- conf = cPickle.load(f)
- except:
- break
- f.close()
- if objtype and objtype in conf:
- conf = conf[objtype]
- else:
- # load all object types that could be in infile
- newconf = {}
- for ky in [MGR_OBJ_SERVER, MGR_OBJ_SCHED, MGR_OBJ_NODE]:
- if ky not in conf:
- conf[ky] = {}
- newconf = dict(newconf.items() + conf[ky].items())
- conf = newconf
- for k, v in conf.items():
- fn = self.du.create_temp_file()
- with open(fn, 'w') as fd:
- # handle server data saved as output of qmgr commands
- # by piping data back into qmgr
- if k.startswith('qmgr_'):
- qmgr = os.path.join(self.client_conf['PBS_EXEC'],
- 'bin', 'qmgr')
- fd.write("\n".join(v))
- self.du.run_cmd(self.hostname, [qmgr], cstdin=fd,
- sudo=True)
- else:
- fd.write("\n".join(v))
- # append the last line
- fd.write("\n")
- self.du.run_cmd(self.hostname, ['cp', fn, k],
- sudo=True)
- os.remove(fn)
- return True
- return False
- def get_tempdir(self):
- """
- platform independent call to get a temporary directory
- """
- return self.du.get_tempdir(self.hostname)
- def __str__(self):
- return (self.__class__.__name__ + ' ' + self.hostname + ' config ' +
- self.pbs_conf_file)
- def __repr__(self):
- return (self.__class__.__name__ + '/' + self.pbs_conf_file + '@' +
- self.hostname)
- class Comm(PBSService):
- """
- PBS ``Comm`` configuration and control
- """
- """
- :param name: The hostname of the Comm. Defaults to current hostname.
- :type name: str
- :param attrs: Dictionary of attributes to set, these will override
- defaults.
- :type attrs: dictionary
- :param pbsconf_file: path to config file to parse for PBS_HOME,
- PBS_EXEC, etc
- :type pbsconf_file: str or None
- :param diagmap: A dictionary of PBS objects (node,server,etc) to
- mapped files from PBS diag directory
- :type diagmap: dictionary
- :param diag: path to PBS diag directory (This will override diagmap)
- :type diag: str or None
- :param server: A PBS server instance to which this Comm is associated
- :type server: str
- :param db_access: set to either file containing credentials to DB access or
- dictionary containing {'dbname':...,'user':...,
- 'port':...}
- :type db_access: str or dictionary
- """
- dflt_attributes = {}
- def __init__(self, name=None, attrs={}, pbsconf_file=None, diagmap={},
- diag=None, server=None, db_access=None):
- self.logger = logging.getLogger(__name__)
- if server is not None:
- self.server = server
- if diag is None and self.server.diag is not None:
- diag = self.server.diag
- if (len(diagmap) == 0) and (len(self.server.diagmap) != 0):
- diagmap = self.server.diagmap
- else:
- self.server = Server(name, pbsconf_file=pbsconf_file,
- db_access=db_access, diag=diag,
- diagmap=diagmap)
- PBSService.__init__(self, name, attrs, self.dflt_attributes,
- pbsconf_file, diagmap, diag)
- _m = ['Comm ', self.shortname]
- if pbsconf_file is not None:
- _m += ['@', pbsconf_file]
- _m += [': ']
- self.logprefix = "".join(_m)
- self.conf_to_cmd_map = {
- 'PBS_COMM_ROUTERS': '-r',
- 'PBS_COMM_THREADS': '-t'
- }
- self.pi = PBSInitServices(hostname=self.hostname,
- conf=self.pbs_conf_file)
- def isUp(self):
- """
- Check for comm up
- """
- return super(Comm, self)._isUp(self)
- def signal(self, sig):
- """
- Send signal to comm
- """
- self.logger.info(self.logprefix + 'sent signal ' + sig)
- return super(Comm, self)._signal(sig, inst=self)
- def get_pid(self):
- """
- Get the comm pid
- """
- return super(Comm, self)._get_pid(inst=self)
- def all_instance_pids(self):
- """
- Get all pids of given instance
- """
- return super(Comm, self)._all_instance_pids(inst=self)
- def start(self, args=None, launcher=None):
- """
- Start the comm
- :param args: Argument required to start the comm
- :type args: str
- :param launcher: Optional utility to invoke the launch of the service
- :type launcher: str or list
- """
- if args is not None or launcher is not None:
- return super(Comm, self)._start(inst=self, args=args,
- cmd_map=self.conf_to_cmd_map,
- launcher=launcher)
- else:
- try:
- rv = self.pi.start_comm()
- self._update_pid(self)
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg)
- return rv
- def stop(self, sig=None):
- """
- Stop the comm.
- :param sig: Signal to stop the comm
- :type sig: str
- """
- if sig is not None:
- self.logger.info(self.logprefix + 'stopping Comm on host ' +
- self.hostname)
- return super(Comm, self)._stop(sig, inst=self)
- else:
- try:
- self.pi.stop_comm()
- self.pid = None
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg)
- return True
- def restart(self):
- """
- Restart the comm.
- """
- if self.isUp():
- if not self.stop():
- return False
- return self.start()
- def log_match(self, msg=None, id=None, n=50, tail=True, allmatch=False,
- regexp=False, max_attempts=None, interval=None,
- starttime=None, endtime=None, level=logging.INFO,
- existence=True):
- """
- Match given ``msg`` in given ``n`` lines of Comm log
- :param msg: log message to match, can be regex also when
- ``regexp`` is True
- :type msg: str
- :param id: The id of the object to trace. Only used for
- tracejob
- :type id: str
- :param n: 'ALL' or the number of lines to search through,
- defaults to 50
- :type n: str or int
- :param tail: If true (default), starts from the end of
- the file
- :type tail: bool
- :param allmatch: If True all matching lines out of then
- parsed are returned as a list. Defaults
- to False
- :type allmatch: bool
- :param regexp: If true msg is a Python regular expression.
- Defaults to False
- :type regexp: bool
- :param max_attempts: the number of attempts to make to find
- a matching entry
- :type max_attempts: int
- :param interval: the interval between attempts
- :type interval: int
- :param starttime: If set ignore matches that occur before
- specified time
- :type starttime: int
- :param endtime: If set ignore matches that occur after
- specified time
- :type endtime: int
- :param level: The logging level, defaults to INFO
- :type level: int
- :param existence: If True (default), check for existence of
- given msg, else check for non-existence of
- given msg.
- :type existence: bool
- :return: (x,y) where x is the matching line
- number and y the line itself. If allmatch is True,
- a list of tuples is returned.
- :rtype: tuple
- :raises PtlLogMatchError:
- When ``existence`` is True and given
- ``msg`` is not found in ``n`` line
- Or
- When ``existence`` is False and given
- ``msg`` found in ``n`` line.
- .. note:: The matching line number is relative to the record
- number, not the absolute line number in the file.
- """
- return self._log_match(self, msg, id, n, tail, allmatch, regexp,
- max_attempts, interval, starttime, endtime,
- level=level, existence=existence)
- class Server(PBSService):
- """
- PBS server ``configuration`` and ``control``
- The Server class is a container to PBS server attributes
- and implements wrappers to the ``IFL API`` to perform
- operations on the server. For example to submit, status,
- delete, manage, etc... jobs, reservations and configurations.
- This class also offers higher-level routines to ease testing,
- see functions, for ``example: revert_to_defaults,
- init_logging, expect, counter.``
- The ptl_conf dictionary holds general configuration for the
- framework's operations, specifically, one can control:
- mode: set to ``PTL_CLI`` to operate in ``CLI`` mode or
- ``PTL_API`` to operate in ``API`` mode
- expect_max_attempts: the default maximum number of attempts
- to be used\ by expect. Defaults to 60
- expect_interval: the default time interval (in seconds)
- between expect\ requests. Defaults to 0.5
- update_attributes: the default on whether Object attributes
- should be\ updated using a list of dictionaries. Defaults
- to True
- :param name: The hostname of the server. Defaults to
- calling pbs_default()
- :type name: str
- :param attrs: Dictionary of attributes to set, these will
- override defaults.
- :type attrs: Dictionary
- :param defaults: Dictionary of default attributes.
- Default: dflt_attributes
- :type defaults: Dictionary
- :param pbsconf_file: path to config file to parse for PBS_HOME,
- PBS_EXEC, etc
- :type pbsconf_file: str
- :param diagmap: A dictionary of PBS objects (node,server,etc)
- to mapped files from PBS diag directory
- :type diagmap: Dictionary
- :param diag: path to PBS diag directory (This will overrides
- diagmap)
- :type diag: str
- :param client: The host to use as client for CLI queries.
- Defaults to the local hostname.
- :type client: str
- :param client_pbsconf_file: The path to a custom PBS_CONF_FILE
- on the client host. Defaults to
- the same path as pbsconf_file.
- :type client_pbsconf_file: str
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- {'dbname':...,'user':...,'port':...}
- :param stat: if True, stat the server attributes
- :type stat: bool
- """
- logger = logging.getLogger(__name__)
- dflt_attributes = {
- ATTR_scheduling: "True",
- ATTR_dfltque: "workq",
- ATTR_logevents: "511",
- ATTR_mailfrom: "adm",
- ATTR_queryother: "True",
- ATTR_rescdflt + ".ncpus": "1",
- ATTR_DefaultChunk + ".ncpus": "1",
- ATTR_schedit: "600",
- ATTR_ResvEnable: "True",
- ATTR_nodefailrq: "310",
- ATTR_maxarraysize: "10000",
- ATTR_license_linger: "3600",
- ATTR_EligibleTimeEnable: "False",
- ATTR_max_concurrent_prov: "5",
- ATTR_FlatUID: 'True',
- }
- dflt_sched_name = 'default'
- ptl_conf = {
- 'mode': PTL_API,
- 'expect_max_attempts': 60,
- 'expect_interval': 0.5,
- 'update_attributes': True,
- }
- # this pattern is a bit relaxed to match common developer build numbers
- version_tag = re.compile("[a-zA-Z_]*(?P<version>[\d\.]+.[\w\d\.]*)[\s]*")
- actions = ExpectActions()
- def __init__(self, name=None, attrs={}, defaults={}, pbsconf_file=None,
- diagmap={}, diag=None, client=None, client_pbsconf_file=None,
- db_access=None, stat=True):
- self.jobs = {}
- self.nodes = {}
- self.reservations = {}
- self.queues = {}
- self.resources = {}
- self.hooks = {}
- self.pbshooks = {}
- self.entities = {}
- self.schedulers = {}
- self.version = None
- self.default_queue = None
- self.last_error = [] # type: array. Set for CLI IFL errors. Not reset
- self.last_out = [] # type: array. Set for CLI IFL output. Not reset
- self.last_rc = None # Set for CLI IFL return code. Not thread-safe
- self.moms = {}
- # default timeout on connect/disconnect set to 60s to mimick the qsub
- # buffer introduced in PBS 11
- self._conn_timeout = 60
- self._conn_timer = None
- self._conn = None
- self._db_conn = None
- self.current_user = pwd.getpwuid(os.getuid())[0]
- if len(defaults.keys()) == 0:
- defaults = self.dflt_attributes
- self.pexpect_timeout = 15
- self.pexpect_sleep_time = .1
- PBSService.__init__(self, name, attrs, defaults, pbsconf_file, diagmap,
- diag)
- _m = ['server ', self.shortname]
- if pbsconf_file is not None:
- _m += ['@', pbsconf_file]
- _m += [': ']
- self.logprefix = "".join(_m)
- self.pi = PBSInitServices(hostname=self.hostname,
- conf=self.pbs_conf_file)
- self.set_client(client)
- if client_pbsconf_file is None:
- self.client_pbs_conf_file = self.du.get_pbs_conf_file(self.client)
- else:
- self.client_pbs_conf_file = client_pbsconf_file
- self.client_conf = self.du.parse_pbs_config(
- self.client, file=self.client_pbs_conf_file)
- if self.client_pbs_conf_file == '/etc/pbs.conf':
- self.default_client_pbs_conf = True
- elif (('PBS_CONF_FILE' not in os.environ) or
- (os.environ['PBS_CONF_FILE'] != self.client_pbs_conf_file)):
- self.default_client_pbs_conf = False
- else:
- self.default_client_pbs_conf = True
- a = {}
- if os.getuid() == 0:
- a = {ATTR_aclroot: 'root'}
- self.dflt_attributes.update(a)
- if not API_OK:
- # mode must be set before the first stat call
- self.set_op_mode(PTL_CLI)
- if stat:
- try:
- tmp_attrs = self.status(SERVER, level=logging.DEBUG,
- db_access=db_access)
- except (PbsConnectError, PbsStatusError):
- tmp_attrs = None
- if tmp_attrs is not None and len(tmp_attrs) > 0:
- self.attributes = tmp_attrs[0]
- if ATTR_dfltque in self.attributes:
- self.default_queue = self.attributes[ATTR_dfltque]
- self.update_version_info()
- def update_version_info(self):
- """
- Update the version information.
- """
- if ATTR_version not in self.attributes:
- self.attributes[ATTR_version] = 'unknown'
- else:
- m = self.version_tag.match(self.attributes[ATTR_version])
- if m:
- v = m.group('version')
- self.version = LooseVersion(v)
- self.logger.info(self.logprefix + 'version ' +
- self.attributes[ATTR_version])
- @classmethod
- def set_update_attributes(cls, val):
- """
- Set update attributes
- """
- cls.logger.info('setting update attributes ' + str(val))
- if val == 1 or val[0] in ('t', 'T'):
- val = True
- else:
- val = False
- cls.ptl_conf['update_attributes'] = val
- @classmethod
- def set_expect_max_attempts(cls, val):
- """
- Set expect max attempts
- """
- cls.logger.info('setting expect max attempts ' + str(val))
- cls.ptl_conf['expect_max_attempts'] = int(val)
- @classmethod
- def set_expect_interval(cls, val):
- """
- Set expect interval
- """
- cls.logger.info('setting expect interval ' + str(val))
- cls.ptl_conf['expect_interval'] = float(val)
- def set_client(self, name=None):
- """
- Set server client
- :param name: Client name
- :type name: str
- """
- if name is None:
- self.client = socket.gethostname()
- else:
- self.client = name
- def _connect(self, hostname, attempt=1):
- if ((self._conn is None or self._conn < 0) or
- (self._conn_timeout == 0 or self._conn_timer is None)):
- self._conn = pbs_connect(hostname)
- self._conn_timer = time.time()
- if self._conn is None or self._conn < 0:
- if attempt > 5:
- m = self.logprefix + 'unable to connect'
- raise PbsConnectError(rv=None, rc=-1, msg=m)
- else:
- self._disconnect(self._conn, force=True)
- time.sleep(1)
- return self._connect(hostname, attempt + 1)
- return self._conn
- def _disconnect(self, conn, force=False):
- """
- disconnect a connection to a Server.
- For performance of the API calls, a connection is
- maintained up to _conn_timer, unless the force parameter
- is set to True
- :param conn: Server connection
- :param force: If true then diconnect forcefully
- :type force: bool
- """
- if ((conn is not None and conn >= 0) and
- (force or
- (self._conn_timeout == 0 or
- (self._conn_timer is not None and
- (time.time() - self._conn_timer > self._conn_timeout))))):
- pbs_disconnect(conn)
- self._conn_timer = None
- self._conn = None
- def set_connect_timeout(self, timeout=0):
- """
- Set server connection timeout
- :param timeout: Timeout value
- :type timeout: int
- """
- self._conn_timeout = timeout
- def get_op_mode(self):
- """
- Returns operating mode for calls to the PBS server.
- Currently, two modes are supported, either the ``API``
- or the ``CLI``. Default is ``API``
- """
- if (not API_OK or (self.ptl_conf['mode'] == PTL_CLI)):
- return PTL_CLI
- return PTL_API
- def set_op_mode(self, mode):
- """
- set operating mode to one of either ``PTL_CLI`` or
- ``PTL_API``.Returns the mode that was set which can
- be different from the value requested, for example, if
- requesting to set ``PTL_API``, in the absence of the
- appropriate SWIG wrappers, the library will fall back to
- ``CLI``, or if requesting ``PTL_CLI`` and there is no
- ``PBS_EXEC`` on the system, None is returned.
- :param mode: Operating mode
- :type mode: str
- """
- if mode == PTL_API:
- if self._conn is not None or self._conn < 0:
- self._conn = None
- if not API_OK:
- self.logger.error(self.logprefix +
- 'API submission is not available')
- return PTL_CLI
- elif mode == PTL_CLI:
- if ((not self.has_diag) and
- not os.path.isdir(os.path.join(self.client_conf['PBS_EXEC'],
- 'bin'))):
- self.logger.error(self.logprefix +
- 'PBS commands are not available')
- return None
- else:
- self.logger.error(self.logprefix + "Unrecognized operating mode")
- return None
- self.ptl_conf['mode'] = mode
- self.logger.info(self.logprefix + 'server operating mode set to ' +
- mode)
- return mode
- def add_expect_action(self, name=None, action=None):
- """
- Add an action handler to expect. Expect Actions are
- custom handlers that are triggered when an unexpected
- value is encountered
- :param name: Action name
- :type name: str or None
- :param action: Action to add
- """
- if name is None and action.name is None:
- return
- if name is None and action.name is not None:
- name = action.name
- if not self.actions.has_action(name):
- self.actions.add_action(action, self.shortname)
- def set_attributes(self, a={}):
- """
- set server attributes
- :param a: Attribute dictionary
- :type a: Dictionary
- """
- super(Server, self).set_attributes(a)
- self.__dict__.update(a)
- def isUp(self):
- """
- returns ``True`` if server is up and ``False`` otherwise
- """
- if self.has_diag:
- return True
- i = 0
- op_mode = self.get_op_mode()
- if ((op_mode == PTL_API) and (self._conn is not None)):
- self._disconnect(self._conn, force=True)
- while i < 20:
- rv = False
- try:
- if op_mode == PTL_CLI:
- self.status(SERVER, level=logging.DEBUG, logerr=False)
- else:
- c = self._connect(self.hostname)
- self._disconnect(c, force=True)
- return True
- except (PbsConnectError, PbsStatusError):
- # if the status/connect operation fails then there might be
- # chances that server process is running but not responsive
- # so we wait until the server is reported operational.
- rv = self._isUp(self)
- # We really mean to check != False rather than just "rv"
- if str(rv) != 'False':
- self.logger.warning('Server process started' +
- 'but not up yet')
- time.sleep(1)
- i += 1
- else:
- # status/connect failed + no server process means
- # server is actually down
- return False
- return False
- def signal(self, sig):
- """
- Send signal to server
- :param sig: Signal to send
- :type sig: str
- """
- self.logger.info('server ' + self.shortname + ': sent signal ' + sig)
- return super(Server, self)._signal(sig, inst=self)
- def get_pid(self):
- """
- Get the server pid
- """
- return super(Server, self)._get_pid(inst=self)
- def all_instance_pids(self):
- """
- Get all pids for a given instance
- """
- return super(Server, self)._all_instance_pids(inst=self)
- def start(self, args=None, launcher=None):
- """
- Start the PBS server
- :param args: Argument required to start the server
- :type args: str
- :param launcher: Optional utility to invoke the launch of the service
- :type launcher: str or list
- """
- if args is not None or launcher is not None:
- rv = super(Server, self)._start(inst=self, args=args,
- launcher=launcher)
- else:
- try:
- rv = self.pi.start_server()
- self._update_pid(self)
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg)
- if self.isUp():
- return rv
- else:
- raise PbsServiceError(rv=False, rc=1, msg=rv['err'])
- def stop(self, sig=None):
- """
- Stop the PBS server
- :param sig: Signal to stop PBS server
- :type sig: str
- """
- if sig is not None:
- self.logger.info(self.logprefix + 'stopping Server on host ' +
- self.hostname)
- rc = super(Server, self)._stop(sig, inst=self)
- else:
- try:
- self.pi.stop_server()
- self.pid = None
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg,
- post=self._disconnect, conn=self._conn,
- force=True)
- rc = True
- self._disconnect(self._conn, force=True)
- return rc
- def restart(self):
- """
- Terminate and start a PBS server.
- """
- if self.isUp():
- if not self.stop():
- return False
- return self.start()
- def log_match(self, msg=None, id=None, n=50, tail=True, allmatch=False,
- regexp=False, max_attempts=None, interval=None,
- starttime=None, endtime=None, level=logging.INFO,
- existence=True):
- """
- Match given ``msg`` in given ``n`` lines of Server log
- :param msg: log message to match, can be regex also when
- ``regexp`` is True
- :type msg: str
- :param id: The id of the object to trace. Only used for
- tracejob
- :type id: str
- :param n: 'ALL' or the number of lines to search through,
- defaults to 50
- :type n: str or int
- :param tail: If true (default), starts from the end of
- the file
- :type tail: bool
- :param allmatch: If True all matching lines out of then
- parsed are returned as a list. Defaults
- to False
- :type allmatch: bool
- :param regexp: If true msg is a Python regular expression.
- Defaults to False
- :type regexp: bool
- :param max_attempts: the number of attempts to make to find
- a matching entry
- :type max_attempts: int
- :param interval: the interval between attempts
- :type interval: int
- :param starttime: If set ignore matches that occur before
- specified time
- :type starttime: int
- :param endtime: If set ignore matches that occur after
- specified time
- :type endtime: int
- :param level: The logging level, defaults to INFO
- :type level: int
- :param existence: If True (default), check for existence of
- given msg, else check for non-existence of
- given msg.
- :type existence: bool
- :return: (x,y) where x is the matching line
- number and y the line itself. If allmatch is True,
- a list of tuples is returned.
- :rtype: tuple
- :raises PtlLogMatchError:
- When ``existence`` is True and given
- ``msg`` is not found in ``n`` line
- Or
- When ``existence`` is False and given
- ``msg`` found in ``n`` line.
- .. note:: The matching line number is relative to the record
- number, not the absolute line number in the file.
- """
- return self._log_match(self, msg, id, n, tail, allmatch, regexp,
- max_attempts, interval, starttime, endtime,
- level=level, existence=existence)
- def revert_to_defaults(self, reverthooks=True, revertqueues=True,
- revertresources=True, delhooks=True,
- delqueues=True, delscheds=True, server_stat=None):
- """
- reset server attributes back to out of box defaults.
- :param reverthooks: If True disable all hooks. Defaults
- to True
- :type reverthooks: bool
- :param revertqueues: If True disable all non-default
- queues. Defaults to True
- :type revertqueues: bool
- :param revertresources: If True, resourcedef file is
- removed. Defaults to True.
- Reverting resources causes a server
- restart to occur.
- :type revertresources: bool
- :param delhooks: If True, hooks are deleted, if deletion
- fails, fall back to reverting hooks. Defaults
- to True.
- :type delhooks: bool
- :param delqueues: If True, all non-default queues are deleted,
- will attempt to delete all jobs first, if it
- fails, revertqueues will be honored,
- otherwise,revertqueues is ignored. Defaults
- to True
- :type delqueues: bool
- :param delscheds: If True all non-default schedulers are deleted
- The sched_priv and sched_logs directories will be
- deleted.
- :type delscheds: bool
- :returns: True upon success and False if an error is
- encountered.
- :raises: PbsStatusError or PbsManagerError
- """
- ignore_attrs = ['id', 'pbs_license', ATTR_NODE_ProvisionEnable]
- ignore_attrs += [ATTR_status, ATTR_total, ATTR_count]
- ignore_attrs += [ATTR_rescassn, ATTR_FLicenses, ATTR_SvrHost]
- ignore_attrs += [ATTR_license_count, ATTR_version, ATTR_managers]
- ignore_attrs += [ATTR_pbs_license_info, ATTR_power_provisioning]
- unsetlist = []
- setdict = {}
- self.logger.info(self.logprefix +
- 'reverting configuration to defaults')
- self.cleanup_jobs_and_reservations()
- self.mpp_hook = os.path.join(self.pbs_conf['PBS_HOME'],
- 'server_priv', 'hooks',
- 'PBS_translate_mpp.HK')
- self.dflt_mpp_hook = os.path.join(self.pbs_conf['PBS_EXEC'],
- 'lib', 'python', 'altair',
- 'pbs_hooks',
- 'PBS_translate_mpp.HK')
- if server_stat is None:
- server_stat = self.status(SERVER, level=logging.DEBUG)[0]
- for k in server_stat.keys():
- if (k in ignore_attrs) or (k in self.dflt_attributes.keys()):
- continue
- elif (('.' in k) and (k.split('.')[0] in ignore_attrs)):
- continue
- else:
- unsetlist.append(k)
- if len(unsetlist) != 0:
- self.manager(MGR_CMD_UNSET, MGR_OBJ_SERVER, unsetlist)
- for k in self.dflt_attributes.keys():
- if(k not in self.attributes or
- self.attributes[k] != self.dflt_attributes[k]):
- setdict[k] = self.dflt_attributes[k]
- if self.platform == 'cray' or self.platform == 'craysim':
- setdict[ATTR_restrict_res_to_release_on_suspend] = 'ncpus'
- if delhooks:
- if self.platform == 'cray' or self.platform == 'craysim':
- reverthooks = True
- else:
- reverthooks = False
- hooks = self.status(HOOK, level=logging.DEBUG)
- hooks = [h['id'] for h in hooks]
- if len(hooks) > 0:
- self.manager(MGR_CMD_DELETE, HOOK, id=hooks, expect=True)
- if delqueues:
- revertqueues = False
- queues = self.status(QUEUE, level=logging.DEBUG)
- queues = [q['id'] for q in queues]
- if len(queues) > 0:
- try:
- nodes = self.status(VNODE, logerr=False)
- for node in nodes:
- if 'queue' in node.keys():
- self.manager(MGR_CMD_UNSET, NODE, 'queue',
- node['id'])
- except:
- pass
- self.manager(MGR_CMD_DELETE, QUEUE, id=queues, expect=True)
- a = {ATTR_qtype: 'Execution',
- ATTR_enable: 'True',
- ATTR_start: 'True'}
- self.manager(MGR_CMD_CREATE, QUEUE, a, id='workq', expect=True)
- setdict.update({ATTR_dfltque: 'workq'})
- if delscheds:
- self.manager(MGR_CMD_LIST, SCHED)
- for name in self.schedulers.keys():
- if name != 'default':
- self.schedulers[name].terminate()
- sched_log = self.schedulers[
- name].attributes['sched_log']
- sched_priv = self.schedulers[
- name].attributes['sched_priv']
- self.du.rm(path=sched_log, sudo=True,
- recursive=True, force=True)
- self.du.rm(path=sched_priv, sudo=True,
- recursive=True, force=True)
- self.manager(MGR_CMD_DELETE, SCHED, id=name)
- if reverthooks:
- if self.platform == 'cray' or self.platform == 'craysim':
- if self.du.cmp(self.hostname, self.dflt_mpp_hook,
- self.mpp_hook, sudo=True) != 0:
- self.du.run_copy(self.hostname, self.dflt_mpp_hook,
- self.mpp_hook, mode=0644, sudo=True)
- self.signal('-HUP')
- hooks = self.status(HOOK, level=logging.DEBUG)
- hooks = [h['id'] for h in hooks]
- a = {ATTR_enable: 'false'}
- if len(hooks) > 0:
- self.manager(MGR_CMD_SET, MGR_OBJ_HOOK, a, hooks,
- expect=True)
- if revertqueues:
- self.status(QUEUE, level=logging.DEBUG)
- queues = []
- for (qname, qobj) in self.queues.items():
- # skip reservation queues. This syntax for Python 2.4
- # compatibility
- if (qname.startswith('R') or qname.startswith('S') or
- qname == server_stat[ATTR_dfltque]):
- continue
- qobj.revert_to_defaults()
- queues.append(qname)
- a = {ATTR_enable: 'false'}
- self.manager(MGR_CMD_SET, QUEUE, a, id=queues, expect=True)
- a = {ATTR_enable: 'True', ATTR_start: 'True'}
- self.manager(MGR_CMD_SET, MGR_OBJ_QUEUE, a,
- id=server_stat[ATTR_dfltque], expect=True)
- if len(setdict) > 0:
- self.manager(MGR_CMD_SET, MGR_OBJ_SERVER, setdict)
- if revertresources:
- try:
- rescs = self.status(RSC)
- rescs = [r['id'] for r in rescs]
- except:
- rescs = []
- if len(rescs) > 0:
- self.manager(MGR_CMD_DELETE, RSC, id=rescs, expect=True)
- return True
- def save_configuration(self, outfile, mode='a'):
- """
- Save a server configuration, this includes:
- - ``server_priv/resourcedef``
- - ``qmgr -c "print server"``
- - ``qmgr -c "print sched"``
- - ``qmgr -c "print hook"``
- :param outfile: the output file to which onfiguration is
- saved
- :type outfile: str
- :param mode: The mode in which to open outfile to save
- configuration. The first object being saved
- should open this file with 'w' and subsequent
- calls from other objects should save with
- mode 'a' or 'a+'. Defaults to a+
- :type mode: str
- :returns: True on success, False on error
- """
- conf = {}
- sconf = {MGR_OBJ_SERVER: conf}
- rd = os.path.join(self.pbs_conf['PBS_HOME'], 'server_priv',
- 'resourcedef')
- self._save_config_file(conf, rd)
- qmgr = os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qmgr')
- ret = self.du.run_cmd(self.client, [qmgr, '-c', 'p s'], sudo=True)
- if ret['rc'] != 0:
- return False
- else:
- conf['qmgr_print_server'] = ret['out']
- ret = self.du.run_cmd(self.hostname, [qmgr, '-c', 'p sched'],
- sudo=True)
- if ret['rc'] != 0:
- return False
- else:
- conf['qmgr_print_sched'] = ret['out']
- ret = self.du.run_cmd(self.hostname, [qmgr, '-c', 'p h'], sudo=True)
- if ret['rc'] != 0:
- return False
- else:
- conf['qmgr_print_hook'] = ret['out']
- try:
- f = open(outfile, mode)
- cPickle.dump(sconf, f)
- f.close()
- except:
- self.logger.error('Error processing file ' + outfile)
- return False
- return True
- def load_configuration(self, infile):
- """
- load configuration from saved file ``infile``
- """
- self.revert_to_defaults()
- self._load_configuration(infile, MGR_OBJ_SERVER)
- def get_hostname(self):
- """
- return the default server hostname
- """
- if self.get_op_mode() == PTL_CLI:
- return self.hostname
- return pbs_default()
- def _db_connect(self, db_access=None):
- if self._db_conn is None:
- if 'user' not in db_access or\
- 'password' not in db_access:
- self.logger.error('missing credentials to access DB')
- return None
- if 'dbname' not in db_access:
- db_access['dbname'] = 'pbs_datastore'
- if 'port' not in db_access:
- db_access['port'] = '15007'
- if 'host' not in db_access:
- db_access['host'] = self.hostname
- user = db_access['user']
- dbname = db_access['dbname']
- port = db_access['port']
- password = db_access['password']
- host = db_access['host']
- cred = "host=%s dbname=%s user=%s password=%s port=%s" % \
- (host, dbname, user, password, port)
- self._db_conn = psycopg2.connect(cred)
- return self._db_conn
- def _db_server_host(self, cur=None, db_access=None):
- """
- Get the server host name from the database. The server
- host name is stored in the pbs.server table and not in
- pbs.server_attr.
- :param cur: Optional, a predefined cursor to use to
- operate on the DB
- :param db_acccess: set to either file containing
- credentials to DB access or
- dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- """
- local_init = False
- if cur is None:
- conn = self._db_connect(db_access)
- local_init = True
- if conn is None:
- return None
- cur = conn.cursor()
- # obtain server name. The server hostname is stored in table
- # pbs.server
- cur.execute('SELECT sv_hostname from pbs.server')
- if local_init:
- conn.commit()
- tmp_query = cur.fetchone()
- if len(tmp_query) > 0:
- svr_host = tmp_query[0]
- else:
- svr_host = "unknown"
- return svr_host
- def status_db(self, obj_type=None, attrib=None, id=None, db_access=None,
- logerr=True):
- """
- Status PBS objects from the SQL database
- :param obj_type: The type of object to query, one of the
- * objects,\ Default: SERVER
- :param attrib: Attributes to query, can a string, a list,
- a dictionary\ Default: None. All attributes
- will be queried
- :type attrib: str or list or dictionary
- :param id: An optional identifier, the name of the object
- to status
- :type id: str
- :param db_access: information needed to access the database,
- can be either a file containing user,
- port, dbname, password info or a
- dictionary of key/value entries
- :type db_access: str or dictionary
- """
- if not PSYCOPG:
- self.logger.error('psycopg module unavailable, install from ' +
- 'http://initd.org/psycopg/ and retry')
- return None
- if not isinstance(db_access, dict):
- try:
- f = open(db_access, 'r')
- except IOError:
- self.logger.error('Unable to access ' + db_access)
- return None
- lines = f.readlines()
- db_access = {}
- for line in lines:
- (k, v) = line.split('=')
- db_access[k] = v
- conn = self._db_connect(db_access)
- if conn is None:
- return None
- cur = conn.cursor()
- stmt = []
- if obj_type == SERVER:
- stmt = ["SELECT sv_name,attr_name,attr_resource,attr_value " +
- "FROM pbs.server_attr"]
- svr_host = self.hostname # self._db_server_host(cur)
- elif obj_type == SCHED:
- stmt = ["SELECT sched_name,attr_name,attr_resource,attr_value " +
- "FROM pbs.scheduler_attr"]
- # reuse server host name for sched host
- svr_host = self.hostname
- elif obj_type == JOB:
- stmt = ["SELECT ji_jobid,attr_name,attr_resource,attr_value " +
- "FROM pbs.job_attr"]
- if id:
- id_stmt = ["ji_jobid='" + id + "'"]
- elif obj_type == QUEUE:
- stmt = ["SELECT qu_name,attr_name,attr_resource,attr_value " +
- "FROM pbs.queue_attr"]
- if id:
- id_stmt = ["qu_name='" + id + "'"]
- elif obj_type == RESV:
- stmt = ["SELECT ri_resvid,attr_name,attr_resource,attr_value " +
- "FROM pbs.resv_attr"]
- if id:
- id_stmt = ["ri_resvid='" + id + "'"]
- elif obj_type in (NODE, VNODE):
- stmt = ["SELECT nd_name,attr_name,attr_resource,attr_value " +
- "FROM pbs.node_attr"]
- if id:
- id_stmt = ["nd_name='" + id + "'"]
- else:
- self.logger.error('status: object type not handled')
- return None
- if attrib or id:
- stmt += ["WHERE"]
- extra_stmt = []
- if attrib:
- if isinstance(attrib, dict):
- attrs = attrib.keys()
- elif isinstance(attrib, list):
- attrs = attrib
- elif isinstance(attrib, str):
- attrs = attrib.split(',')
- for a in attrs:
- extra_stmt += ["attr_name='" + a + "'"]
- stmt += [" OR ".join(extra_stmt)]
- if id:
- stmt += [" AND ", " AND ".join(id_stmt)]
- exec_stmt = " ".join(stmt)
- self.logger.debug('server: executing db statement: ' + exec_stmt)
- cur.execute(exec_stmt)
- conn.commit()
- _results = cur.fetchall()
- obj_dict = {}
- for _res in _results:
- if obj_type in (SERVER, SCHED):
- obj_name = svr_host
- else:
- obj_name = _res[0]
- if obj_name not in obj_dict:
- obj_dict[obj_name] = {'id': obj_name}
- attr = _res[1]
- if _res[2]:
- attr += '.' + _res[2]
- obj_dict[obj_name][attr] = _res[3]
- return obj_dict.values()
- #
- # Begin IFL Wrappers
- #
- def status(self, obj_type=SERVER, attrib=None, id=None,
- extend=None, level=logging.INFO, db_access=None, runas=None,
- resolve_indirectness=False, logerr=True):
- """
- Stat any PBS object ``[queue, server, node, hook, job,
- resv, sched]``.If the Server is setup from diag input,
- see diag or diagmap member, the status calls are routed
- directly to the data on files from diag.
- The server can be queried either through the 'qstat'
- command line tool or through the wrapped PBS IFL api,
- see set_op_mode.
- Return a dictionary representation of a batch status object
- raises ``PbsStatsuError on error``.
- :param obj_type: The type of object to query, one of the *
- objects.Default: SERVER
- :param attrib: Attributes to query, can be a string, a
- list, a dictionary.Default is to query all
- attributes.
- :type attrib: str or list or dictionary
- :param id: An optional id, the name of the object to status
- :type id: str
- :param extend: Optional extension to the IFL call
- :param level: The logging level, defaults to INFO
- :type level: str
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- :param runas: run stat as user
- :type runas: str
- :param resolve_indirectness: If True resolves indirect node
- resources values
- :type resolve_indirectness: bool
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- In addition to standard IFL stat call, this wrapper handles
- a few cases that aren't implicitly offered by pbs_stat*,
- those are for Hooks,Resources, and a formula evaluation.
- """
- prefix = 'status on ' + self.shortname
- if runas:
- prefix += ' as ' + str(runas)
- prefix += ': '
- self.logit(prefix, obj_type, attrib, id, level)
- bs = None
- bsl = []
- freebs = False
- # 2 - Special handling for gathering the job formula value.
- if attrib is not None and PTL_FORMULA in attrib:
- if (((isinstance(attrib, list) or isinstance(attrib, dict)) and
- (len(attrib) == 1)) or
- (isinstance(attrib, str) and len(attrib.split(',')) == 1)):
- bsl = self.status(
- JOB, 'Resource_List.select', id=id, extend='t')
- if self.schedulers[self.dflt_sched_name] is None:
- self.schedulers[self.dflt_sched_name] = Scheduler(
- self.hostname)
- if 'log_filter' in self.schedulers[
- self.dflt_sched_name].sched_config:
- _prev_filter = self.schedulers[
- self.dflt_sched_name].sched_config[
- 'log_filter']
- if int(_prev_filter) & 2048:
- self.schedulers[self.dflt_sched_name].set_sched_config(
- {'log_filter': 2048})
- self.manager(MGR_CMD_SET, SERVER, {'scheduling': 'True'})
- if id is None:
- _formulas = self.schedulers[self.dflt_sched_name].job_formula()
- else:
- _formulas = {
- id: self.schedulers[
- self.dflt_sched_name].job_formula(
- jobid=id)
- }
- if not int(_prev_filter) & 2048:
- self.schedulers[self.dflt_sched_name].set_sched_config(
- {'log_filter': int(_prev_filter)})
- if len(bsl) == 0:
- bsl = [{'id': id}]
- for _b in bsl:
- if _b['id'] in _formulas:
- _b[PTL_FORMULA] = _formulas[_b['id']]
- return bsl
- # 3- Serve data from database if requested... and available for the
- # given object type
- if db_access and obj_type in (SERVER, SCHED, NODE, QUEUE, RESV, JOB):
- bsl = self.status_db(obj_type, attrib, id, db_access=db_access,
- logerr=logerr)
- # 4- Serve data from diag files
- elif obj_type in self.diagmap:
- if obj_type in (HOOK, PBS_HOOK):
- for f in self.diagmap[obj_type]:
- _b = self.utils.file_to_dictlist(f, attrib)
- if _b and 'hook_name' in _b[0]:
- _b[0]['id'] = _b[0]['hook_name']
- else:
- _b[0]['id'] = os.path.basename(f)
- if id is None or id == _b[0]['id']:
- bsl.extend(_b)
- else:
- bsl = self.utils.file_to_dictlist(self.diagmap[obj_type],
- attrib, id=id)
- # 6- Stat using PBS CLI commands
- elif self.get_op_mode() == PTL_CLI:
- tgt = self.client
- if obj_type in (JOB, QUEUE, SERVER):
- pcmd = [os.path.join(
- self.client_conf['PBS_EXEC'],
- 'bin',
- 'qstat')]
- if extend:
- pcmd += ['-' + extend]
- if obj_type == JOB:
- pcmd += ['-f']
- if id:
- pcmd += [id]
- else:
- pcmd += ['@' + self.hostname]
- elif obj_type == QUEUE:
- pcmd += ['-Qf']
- if id:
- if '@' not in id:
- pcmd += [id + '@' + self.hostname]
- else:
- pcmd += [id]
- else:
- pcmd += ['@' + self.hostname]
- elif obj_type == SERVER:
- pcmd += ['-Bf', self.hostname]
- elif obj_type in (NODE, VNODE, HOST):
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'pbsnodes')]
- pcmd += ['-s', self.hostname]
- if obj_type in (NODE, VNODE):
- pcmd += ['-v']
- if obj_type == HOST:
- pcmd += ['-H']
- if id:
- pcmd += [id]
- else:
- pcmd += ['-a']
- elif obj_type == RESV:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'pbs_rstat')]
- pcmd += ['-f']
- if id:
- pcmd += [id]
- elif obj_type in (SCHED, PBS_HOOK, HOOK, RSC):
- try:
- rc = self.manager(MGR_CMD_LIST, obj_type, attrib, id,
- runas=runas, level=level, logerr=logerr)
- except PbsManagerError, e:
- rc = e.rc
- # PBS bug, no hooks yields a return code of 1, we ignore
- if obj_type != HOOK:
- raise PbsStatusError(
- rc=rc, rv=[], msg=self.geterrmsg())
- if rc == 0:
- if obj_type == HOOK:
- o = self.hooks
- elif obj_type == PBS_HOOK:
- o = self.pbshooks
- elif obj_type == SCHED:
- o = self.schedulers
- elif obj_type == RSC:
- o = self.resources
- if id:
- if id in o:
- return [o[id].attributes]
- else:
- return None
- return [h.attributes for h in o.values()]
- return []
- else:
- self.logger.error(self.logprefix + "unrecognized object type")
- raise PbsStatusError(rc=-1, rv=[],
- msg="unrecognized object type")
- return None
- # as_script is used to circumvent some shells that will not pass
- # along environment variables when invoking a command through sudo
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- elif obj_type == RESV and not self._is_local:
- pcmd = ['PBS_SERVER=' + self.hostname] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(tgt, pcmd, runas=runas, as_script=as_script,
- level=logging.INFOCLI, logerr=logerr)
- o = ret['out']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = ret['rc']
- if ret['rc'] != 0:
- raise PbsStatusError(rc=ret['rc'], rv=[], msg=self.geterrmsg())
- bsl = self.utils.convert_to_dictlist(o, attrib, mergelines=True)
- # 7- Stat with impersonation over PBS IFL swig-wrapped API
- elif runas is not None:
- _data = {'obj_type': obj_type, 'attrib': attrib, 'id': id}
- bsl = self.pbs_api_as('status', user=runas, data=_data,
- extend=extend)
- else:
- # 8- Stat over PBS IFL API
- #
- # resources are special attributes, all resources are queried as
- # a single attribute.
- # e.g. querying the resources_available attribute returns all
- # resources such as ncpus, mem etc. when querying for
- # resources_available.ncpus and resources_available.mem only query
- # resources_available once and retrieve the resources desired from
- # there
- if isinstance(attrib, dict):
- attribcopy = {}
- restype = []
- for k, v in attrib.items():
- if isinstance(v, tuple):
- # SET requires a special handling because status may
- # have been called through counter to count the number
- # of objects have a given attribute set, in this case
- # we set the attribute to an empty string rather than
- # the number of elements requested. This is a
- # side-effect of the way pbs_statjob works
- if v[0] in (SET, MATCH_RE):
- v = ''
- else:
- v = v[1]
- if callable(v):
- v = ''
- if '.' in k:
- _r = k.split('.')[0]
- if _r not in restype:
- attribcopy[k] = v
- restype.append(_r)
- else:
- attribcopy[k] = v
- elif isinstance(attrib, list):
- attribcopy = []
- for k in attrib:
- if '.' in k:
- _found = False
- for _e in attribcopy:
- _r = k.split('.')[0]
- if _r == _e.split('.')[0]:
- _found = True
- break
- if not _found:
- attribcopy.append(k)
- else:
- attribcopy.append(k)
- else:
- attribcopy = attrib
- a = self.utils.convert_to_attrl(attribcopy)
- c = self._connect(self.hostname)
- if obj_type == JOB:
- bs = pbs_statjob(c, id, a, extend)
- elif obj_type == QUEUE:
- bs = pbs_statque(c, id, a, extend)
- elif obj_type == SERVER:
- bs = pbs_statserver(c, a, extend)
- elif obj_type == HOST:
- bs = pbs_statnode(c, id, a, extend)
- elif obj_type == VNODE:
- bs = pbs_statvnode(c, id, a, extend)
- elif obj_type == RESV:
- bs = pbs_statresv(c, id, a, extend)
- elif obj_type == SCHED:
- bs = pbs_statsched(c, a, extend)
- elif obj_type == RSC:
- # up to PBSPro 12.3 pbs_statrsc was not in pbs_ifl.h
- bs = pbs_statrsc(c, id, a, extend)
- elif obj_type in (HOOK, PBS_HOOK):
- if os.getuid() != 0:
- try:
- rc = self.manager(MGR_CMD_LIST, obj_type, attrib,
- id, level=level)
- if rc == 0:
- if id:
- if (obj_type == HOOK and
- id in self.hooks):
- return [self.hooks[id].attributes]
- elif (obj_type == PBS_HOOK and
- id in self.pbshooks):
- return [self.pbshooks[id].attributes]
- else:
- return None
- if obj_type == HOOK:
- return [h.attributes for h in
- self.hooks.values()]
- elif obj_type == PBS_HOOK:
- return [h.attributes for h in
- self.pbshooks.values()]
- except:
- pass
- else:
- bs = pbs_stathook(c, id, a, extend)
- else:
- self.logger.error(self.logprefix +
- "unrecognized object type " + str(obj_type))
- freebs = True
- err = self.geterrmsg()
- self._disconnect(c)
- if err:
- raise PbsStatusError(rc=-1, rv=[], msg=err)
- if not isinstance(bs, list):
- bsl = self.utils.batch_status_to_dictlist(bs, attrib)
- else:
- bsl = self.utils.filter_batch_status(bs, attrib)
- # Update each object's dictionary with corresponding attributes and
- # values
- self.update_attributes(obj_type, bsl)
- # Hook stat is done through CLI, no need to free the batch_status
- if (not isinstance(bs, list) and freebs and
- obj_type not in (HOOK, PBS_HOOK) and os.getuid() != 0):
- pbs_statfree(bs)
- # 9- Resolve indirect resources
- if obj_type in (NODE, VNODE) and resolve_indirectness:
- nodes = {}
- for _b in bsl:
- for k, v in _b.items():
- if v.startswith('@'):
- if v[1:] in nodes:
- _b[k] = nodes[v[1:]][k]
- else:
- for l in bsl:
- if l['id'] == v[1:]:
- nodes[k] = l[k]
- _b[k] = l[k]
- break
- del nodes
- return bsl
- def submit_interactive_job(self, job, cmd):
- """
- submit an ``interactive`` job. Returns a job identifier
- or raises PbsSubmitError on error
- :param cmd: The command to run to submit the interactive
- job
- :type cmd: str
- :param job: the job object. The job must have the attribute
- 'interactive_job' populated. That attribute is
- a list of tuples of the form:
- (<command>, <expected output>, <...>)
- for example to send the command
- hostname and expect 'myhost.mydomain' one would
- set:job.interactive_job =
- [('hostname', 'myhost.mydomain')]
- If more than one lines are expected they are
- appended to the tuple.
- :raises: PbsSubmitError
- """
- ij = InteractiveJob(job, cmd, self.hostname)
- # start the interactive job submission thread and wait to pickup the
- # actual job identifier
- ij.start()
- while ij.jobid is None:
- continue
- return ij.jobid
- def submit(self, obj, script=None, extend=None, submit_dir=None):
- """
- Submit a job or reservation. Returns a job identifier
- or raises PbsSubmitError on error
- :param obj: The Job or Reservation instance to submit
- :param script: Path to a script to submit. Default: None
- as an executable\ /bin/sleep 100 is submitted
- :type script: str or None
- :param extend: Optional extension to the IFL call.
- see pbs_ifl.h
- :type extend: str or None
- :param submit_dir: directory from which job is submitted.
- Defaults to temporary directory
- :type submit_dir: str or None
- :raises: PbsSubmitError
- """
- _interactive_job = False
- as_script = False
- rc = None
- if isinstance(obj, Job):
- if self.platform == 'cray' or self.platform == 'craysim':
- m = False
- vncompute = False
- if 'Resource_List.select' in obj.attributes:
- select = obj.attributes['Resource_List.select']
- start = select.startswith('vntype=cray_compute')
- m = start or ':vntype=cray_compute' in select
- if 'Resource_List.vntype' in obj.attributes:
- vn_type = obj.attributes['Resource_List.vntype']
- if vn_type == 'cray_compute':
- vncompute = True
- if obj.script is not None:
- script = obj.script
- elif m or vncompute:
- aprun_cmd = "aprun -b -B"
- executable = obj.attributes[ATTR_executable]
- start = executable.startswith('aprun ')
- aprun_exist = start or '/aprun' in executable
- if script:
- aprun_cmd += " " + script
- else:
- if aprun_exist:
- aprun_cmd = executable
- else:
- aprun_cmd += " " + executable
- arg_list = obj.attributes[ATTR_Arglist]
- aprun_cmd += " " + self.utils.convert_arglist(arg_list)
- fn = self.du.create_temp_file(hostname=None,
- prefix='PtlPbsJobScript',
- asuser=obj.username,
- body=aprun_cmd)
- self.du.chmod(path=fn, mode=0755)
- script = fn
- elif script is None and obj.script is not None:
- script = obj.script
- if ATTR_inter in obj.attributes:
- _interactive_job = True
- if ATTR_executable in obj.attributes:
- del obj.attributes[ATTR_executable]
- if ATTR_Arglist in obj.attributes:
- del obj.attributes[ATTR_Arglist]
- elif not isinstance(obj, Reservation):
- m = self.logprefix + "unrecognized object type"
- self.logger.error(m)
- return None
- if submit_dir is None:
- submit_dir = pwd.getpwnam(obj.username)[5]
- cwd = os.getcwd()
- os.chdir(submit_dir)
- c = None
- # 1- Submission using the command line tools
- if self.get_op_mode() == PTL_CLI:
- exclude_attrs = [] # list of attributes to not convert to CLI
- if isinstance(obj, Job):
- runcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'qsub')]
- elif isinstance(obj, Reservation):
- runcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'pbs_rsub')]
- if ATTR_resv_start in obj.custom_attrs:
- start = obj.custom_attrs[ATTR_resv_start]
- obj.custom_attrs[ATTR_resv_start] = \
- self.utils.convert_seconds_to_datetime(start)
- if ATTR_resv_end in obj.custom_attrs:
- end = obj.custom_attrs[ATTR_resv_end]
- obj.custom_attrs[ATTR_resv_end] = \
- self.utils.convert_seconds_to_datetime(end)
- if ATTR_resv_timezone in obj.custom_attrs:
- exclude_attrs += [ATTR_resv_timezone, ATTR_resv_standing]
- # handling of impersonation differs widely across OS's,
- # when setting PBS_TZID we standardize on running the cmd
- # as a script instead of customizing for each OS flavor
- _tz = obj.custom_attrs[ATTR_resv_timezone]
- runcmd = ['PBS_TZID=' + _tz] + runcmd
- as_script = True
- if ATTR_resv_rrule in obj.custom_attrs:
- _rrule = obj.custom_attrs[ATTR_resv_rrule]
- if _rrule[0] not in ("'", '"'):
- _rrule = "'" + _rrule + "'"
- obj.custom_attrs[ATTR_resv_rrule] = _rrule
- if not self._is_local:
- if ATTR_queue not in obj.attributes:
- runcmd += ['-q@' + self.hostname]
- elif '@' not in obj.attributes[ATTR_queue]:
- curq = obj.attributes[ATTR_queue]
- runcmd += ['-q' + curq + '@' + self.hostname]
- if obj.custom_attrs and (ATTR_queue in obj.custom_attrs):
- del obj.custom_attrs[ATTR_queue]
- _conf = self.default_client_pbs_conf
- cmd = self.utils.convert_to_cli(obj.custom_attrs, IFL_SUBMIT,
- self.hostname, dflt_conf=_conf,
- exclude_attrs=exclude_attrs)
- if cmd is None:
- try:
- os.chdir(cwd)
- except OSError:
- pass
- return None
- runcmd += cmd
- if script:
- runcmd += [script]
- else:
- if ATTR_executable in obj.attributes:
- runcmd += ['--', obj.attributes[ATTR_executable]]
- if ((ATTR_Arglist in obj.attributes) and
- (obj.attributes[ATTR_Arglist] is not None)):
- args = obj.attributes[ATTR_Arglist]
- arglist = self.utils.convert_arglist(args)
- if arglist is None:
- try:
- os.chdir(cwd)
- except OSError:
- pass
- return None
- runcmd += [arglist]
- if obj.username != self.current_user:
- runas = obj.username
- else:
- runas = None
- if _interactive_job:
- ijid = self.submit_interactive_job(obj, runcmd)
- try:
- os.chdir(cwd)
- except OSError:
- pass
- return ijid
- if not self.default_client_pbs_conf:
- runcmd = [
- 'PBS_CONF_FILE=' + self.client_pbs_conf_file] + runcmd
- as_script = True
- ret = self.du.run_cmd(self.client, runcmd, runas=runas,
- level=logging.INFOCLI, as_script=as_script,
- logerr=False)
- if ret['rc'] != 0:
- objid = None
- else:
- objid = ret['out'][0]
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc = ret['rc']
- # 2- Submission with impersonation over API
- elif obj.username != self.current_user:
- # submit job as a user requires setting uid to that user. It's
- # done in a separate process
- obj.set_variable_list(obj.username, submit_dir)
- obj.set_attributes()
- if (obj.script is not None and not self._is_local):
- # This copy assumes that the file system layout on the
- # remote host is identical to the local host. When not
- # the case, this code will need to be updated to copy
- # to a known remote location and update the obj.script
- self.du.run_copy(self.hostname, obj.script, obj.script)
- os.remove(obj.script)
- objid = self.pbs_api_as('submit', obj, user=obj.username,
- extend=extend)
- # 3- Submission as current user over API
- else:
- c = self._connect(self.hostname)
- if isinstance(obj, Job):
- if script:
- if ATTR_o not in obj.attributes:
- obj.attributes[ATTR_o] = (self.hostname + ':' +
- obj.script + '.o')
- if ATTR_e not in obj.attributes:
- obj.attributes[ATTR_e] = (self.hostname + ':' +
- obj.script + '.e')
- sc = os.path.basename(script)
- obj.unset_attributes([ATTR_executable, ATTR_Arglist])
- if ATTR_N not in obj.custom_attrs:
- obj.attributes[ATTR_N] = sc
- if ATTR_queue in obj.attributes:
- destination = obj.attributes[ATTR_queue]
- # queue must be removed otherwise will cause the submit
- # to fail silently
- del obj.attributes[ATTR_queue]
- else:
- destination = None
- if (ATTR_o not in obj.attributes or
- ATTR_e not in obj.attributes):
- fn = self.utils.random_str(
- length=4, prefix='PtlPbsJob')
- tmp = self.du.get_tempdir(self.hostname)
- fn = os.path.join(tmp, fn)
- if ATTR_o not in obj.attributes:
- obj.attributes[ATTR_o] = (self.hostname + ':' +
- fn + '.o')
- if ATTR_e not in obj.attributes:
- obj.attributes[ATTR_e] = (self.hostname + ':' +
- fn + '.e')
- obj.attropl = self.utils.dict_to_attropl(obj.attributes)
- objid = pbs_submit(c, obj.attropl, script, destination,
- extend)
- elif isinstance(obj, Reservation):
- if ATTR_resv_duration in obj.attributes:
- # reserve_duration is not a valid attribute, the API call
- # will get rejected if it is used
- wlt = ATTR_l + '.walltime'
- obj.attributes[wlt] = obj.attributes[ATTR_resv_duration]
- del obj.attributes[ATTR_resv_duration]
- obj.attropl = self.utils.dict_to_attropl(obj.attributes)
- objid = pbs_submit_resv(c, obj.attropl, extend)
- prefix = 'submit to ' + self.shortname + ' as '
- if isinstance(obj, Job):
- self.logit(prefix + '%s: ' % obj.username, JOB, obj.custom_attrs,
- objid)
- if obj.script_body:
- self.logger.log(logging.INFOCLI, 'job script ' + script +
- '\n---\n' + obj.script_body + '\n---')
- if objid is not None:
- self.jobs[objid] = obj
- elif isinstance(obj, Reservation):
- # Reservations without -I option return as 'R123 UNCONFIRMED'
- # so split to get the R123 only
- self.logit(prefix + '%s: ' % obj.username, RESV, obj.attributes,
- objid)
- if objid is not None:
- objid = objid.split()[0]
- self.reservations[objid] = obj
- if objid is not None:
- obj.server[self.hostname] = objid
- else:
- try:
- os.chdir(cwd)
- except OSError:
- pass
- raise PbsSubmitError(rc=rc, rv=None, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- try:
- os.chdir(cwd)
- except OSError:
- pass
- return objid
- def deljob(self, id=None, extend=None, runas=None, wait=False,
- logerr=True, attr_W=None):
- """
- delete a single job or list of jobs specified by id
- raises ``PbsDeljobError`` on error
- :param id: The identifier(s) of the jobs to delete
- :type id: str or list
- :param extend: Optional parameters to pass along to PBS
- :type extend: str or None
- :param runas: run as user
- :type runas: str or None
- :param wait: Set to True to wait for job(s) to no longer
- be reported by PBS. False by default
- :type wait: bool
- :param logerr: Whether to log errors. Defaults to True.
- :type logerr: bool
- :param attr_w: -W args to qdel (Only for cli mode)
- :type attr_w: str
- :raises: PbsDeljobError
- """
- prefix = 'delete job on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if id is not None:
- if not isinstance(id, list):
- id = id.split(',')
- prefix += ', '.join(id)
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qdel')]
- if extend is not None:
- pcmd += self.utils.convert_to_cli(extend, op=IFL_DELETE,
- hostname=self.hostname)
- if attr_W is not None:
- pcmd += ['-W']
- if attr_W != PTL_NOARG:
- pcmd += [attr_W]
- if id is not None:
- pcmd += id
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- elif not self._is_local:
- pcmd = ['PBS_SERVER=' + self.hostname] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, logerr=logerr,
- level=logging.INFOCLI)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('deljob', id, user=runas, extend=extend)
- else:
- c = self._connect(self.hostname)
- rc = 0
- for ajob in id:
- tmp_rc = pbs_deljob(c, ajob, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsDeljobError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if self.jobs is not None:
- for j in id:
- if j in self.jobs:
- if self.jobs[j].interactive_handle is not None:
- self.jobs[j].interactive_handle.close()
- del self.jobs[j]
- if c:
- self._disconnect(c)
- if wait:
- for oid in id:
- self.expect(JOB, 'queue', id=oid, op=UNSET, runas=runas,
- level=logging.DEBUG)
- return rc
- def delresv(self, id=None, extend=None, runas=None, wait=False,
- logerr=True):
- """
- delete a single job or list of jobs specified by id
- raises ``PbsDeljobError`` on error
- :param id: The identifier(s) of the jobs to delete
- :type id: str or list
- :param extend: Optional parameters to pass along to PBS
- :type extend: str or None
- :param runas: run as user
- :type runas: str or None
- :param wait: Set to True to wait for job(s) to no longer
- be reported by PBS. False by default
- :type wait: bool
- :param logerr: Whether to log errors. Defaults to True.
- :type logerr: bool
- :raises: PbsDeljobError
- """
- prefix = 'delete resv on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if id is not None:
- if not isinstance(id, list):
- id = id.split(',')
- prefix += ', '.join(id)
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'pbs_rdel')]
- if id is not None:
- pcmd += id
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- elif not self._is_local:
- pcmd = ['PBS_SERVER=' + self.hostname] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, logerr=logerr,
- level=logging.INFOCLI)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('delresv', id, user=runas, extend=extend)
- else:
- c = self._connect(self.hostname)
- rc = 0
- for ajob in id:
- tmp_rc = pbs_delresv(c, ajob, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsDelresvError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if self.reservations is not None:
- for j in id:
- if j in self.reservations:
- del self.reservations[j]
- if c:
- self._disconnect(c)
- if wait:
- for oid in id:
- self.expect(RESV, 'queue', id=oid, op=UNSET, runas=runas,
- level=logging.DEBUG)
- return rc
- def delete(self, id=None, extend=None, runas=None, wait=False,
- logerr=True):
- """
- delete a single job or list of jobs specified by id
- raises ``PbsDeleteError`` on error
- :param id: The identifier(s) of the jobs/resvs to delete
- :type id: str or list
- :param extend: Optional parameters to pass along to PBS
- :type extend: str or none
- :param runas: run as user
- :type runas: str
- :param wait: Set to True to wait for job(s)/resv(s) to
- no longer be reported by PBS. False by default
- :type wait: bool
- :param logerr: Whether to log errors. Defaults to True.
- :type logerr: bool
- :raises: PbsDeleteError
- """
- prefix = 'delete on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if id is not None:
- if not isinstance(id, list):
- id = id.split(',')
- prefix += ','.join(id)
- if extend is not None:
- prefix += ' with ' + str(extend)
- self.logger.info(prefix)
- if not len(id) > 0:
- return 0
- obj_type = {}
- for j in id:
- if j[0] in ('R', 'S'):
- obj_type[j] = RESV
- try:
- rc = self.delresv(j, extend, runas, logerr=logerr)
- except PbsDelresvError, e:
- rc = e.rc
- msg = e.msg
- rv = e.rv
- else:
- obj_type[j] = JOB
- try:
- rc = self.deljob(j, extend, runas, logerr=logerr)
- except PbsDeljobError, e:
- rc = e.rc
- msg = e.msg
- rv = e.rv
- if rc != 0:
- raise PbsDeleteError(rc=rc, rv=rv, msg=msg)
- if wait:
- for oid in id:
- self.expect(obj_type[oid], 'queue', id=oid, op=UNSET,
- runas=runas, level=logging.DEBUG)
- return rc
- def select(self, attrib=None, extend=None, runas=None, logerr=True):
- """
- Select jobs that match attributes list or all jobs if no
- attributes raises ``PbsSelectError`` on error
- :param attrib: A string, list, or dictionary of attributes
- :type attrib: str or list or dictionary
- :param extend: the extended attributes to pass to select
- :type extend: str or None
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :returns: A list of job identifiers that match the
- attributes specified
- :raises: PbsSelectError
- """
- prefix = "select on " + self.shortname
- if runas is not None:
- prefix += " as " + str(runas)
- prefix += ": "
- if attrib is None:
- s = PTL_ALL
- elif not isinstance(attrib, dict):
- self.logger.error(prefix + "attributes must be a dictionary")
- return
- else:
- s = str(attrib)
- self.logger.info(prefix + s)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'],
- 'bin', 'qselect')]
- cmd = self.utils.convert_to_cli(attrib, op=IFL_SELECT,
- hostname=self.hostname)
- if extend is not None:
- pcmd += ['-' + extend]
- if not self._is_local and ((attrib is None) or
- (ATTR_queue not in attrib)):
- pcmd += ['-q', '@' + self.hostname]
- pcmd += cmd
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = ret['rc']
- if self.last_rc != 0:
- raise PbsSelectError(rc=self.last_rc, rv=False,
- msg=self.geterrmsg())
- jobs = ret['out']
- # command returns no jobs as empty, since we expect a valid id,
- # we reset the jobs to an empty array
- if len(jobs) == 1 and jobs[0] == '':
- jobs = []
- elif runas is not None:
- jobs = self.pbs_api_as('select', user=runas, data=attrib,
- extend=extend)
- else:
- attropl = self.utils.convert_to_attropl(attrib, op=EQ)
- c = self._connect(self.hostname)
- jobs = pbs_selectjob(c, attropl, extend)
- err = self.geterrmsg()
- if err:
- raise PbsSelectError(rc=-1, rv=False, msg=err,
- post=self._disconnect, conn=c)
- self._disconnect(c)
- return jobs
- def selstat(self, select_list, rattrib, runas=None, extend=None):
- """
- stat and filter jobs attributes.
- :param select_list: The filter criteria
- :type select: List
- :param rattrib: The attributes to query
- :type rattrib: List
- :param runas: run as user
- :type runas: str or None
- .. note:: No ``CLI`` counterpart for this call
- """
- attrl = self.utils.convert_to_attrl(rattrib)
- attropl = self.utils.convert_to_attropl(select_list)
- c = self._connect(self.hostname)
- bs = pbs_selstat(c, attropl, attrl, extend)
- self._disconnect(c)
- return bs
- def manager(self, cmd, obj_type, attrib=None, id=None, extend=None,
- expect=False, max_attempts=None, level=logging.INFO,
- sudo=None, runas=None, logerr=True):
- """
- issue a management command to the server, e.g to set an
- attribute
- Returns 0 for Success and non 0 number for Failure
- :param cmd: The command to issue,
- ``MGR_CMD_[SET,UNSET, LIST,...]`` see pbs_ifl.h
- :type cmd: str
- :param obj_type: The type of object to query, one of
- the * objects
- :param attrib: Attributes to operate on, can be a string, a
- list,a dictionary
- :type attrib: str or list or dictionary
- :param id: The name or list of names of the object(s) to act
- upon.
- :type id: str or list
- :param extend: Optional extension to the IFL call. see
- pbs_ifl.h
- :type extend: str or None
- :param expect: If set to True, query the server expecting
- the value to be\ accurately reflected.
- Defaults to False
- :type expect: bool
- :param max_attempts: Sets a maximum number of attempts to
- call expect with.
- :type max_attempts: int
- :param level: logging level
- :param sudo: If True, run the manager command as super user.
- Defaults to None. Some attribute settings
- should be run with sudo set to True, those are
- acl_roots, job_sort_formula, hook operations,
- no_sched_hook_event, in those cases, setting
- sudo to False is only needed for testing
- purposes
- :type sudo: bool
- :param runas: run as user
- :type runas: str
- :param logerr: If False, CLI commands do not log error,
- i.e. silent mode
- :type logerr: bool
- :raises: PbsManagerError
- :returns: On success:
- - if expect is False, return code of qmgr/pbs_manager
- - if expect is True, 0 for success
- :raises: On Error/Failure:
- - PbsManagerError if qmgr/pbs_manager() failed
- - PtlExpectError if expect() failed
- """
- # Currently, expect() doesn't validate the values being set for
- # create operations.
- # For unset operations, expect does not handle attributes that are
- # reset to default after unset.
- # So, only call expect automatically for set and delete operations.
- if cmd in (MGR_CMD_SET, MGR_CMD_DELETE):
- expect = True
- if isinstance(id, str):
- oid = id.split(',')
- else:
- oid = id
- self.logit('manager on ' + self.shortname +
- [' as ' + str(runas), ''][runas is None] + ': ' +
- PBS_CMD_MAP[cmd] + ' ', obj_type, attrib, oid, level=level)
- c = None # connection handle
- if (self.get_op_mode() == PTL_CLI or
- sudo is not None or
- obj_type in (HOOK, PBS_HOOK) or
- (attrib is not None and ('job_sort_formula' in attrib or
- 'acl_roots' in attrib or
- 'no_sched_hook_event' in attrib))):
- execcmd = [PBS_CMD_MAP[cmd], PBS_OBJ_MAP[obj_type]]
- if oid is not None:
- if cmd == MGR_CMD_DELETE and obj_type == NODE and oid[0] == "":
- oid[0] = "@default"
- execcmd += [",".join(oid)]
- if attrib is not None and cmd != MGR_CMD_LIST:
- if cmd == MGR_CMD_IMPORT:
- execcmd += [attrib['content-type'],
- attrib['content-encoding'],
- attrib['input-file']]
- else:
- if isinstance(attrib, (dict, OrderedDict)):
- kvpairs = []
- for k, v in attrib.items():
- if isinstance(v, tuple):
- if v[0] == INCR:
- op = '+='
- elif v[0] == DECR:
- op = '-='
- else:
- msg = 'Invalid operation: %s' % (v[0])
- raise PbsManagerError(rc=1, rv=False,
- msg=msg)
- v = v[1]
- else:
- op = '='
- # handle string arrays as double quotes if
- # not already set:
- if isinstance(v, str) and ',' in v and v[0] != '"':
- v = '"' + v + '"'
- kvpairs += [str(k) + op + str(v)]
- if kvpairs:
- execcmd += [",".join(kvpairs)]
- del kvpairs
- elif isinstance(attrib, list):
- execcmd += [",".join(attrib)]
- elif isinstance(attrib, str):
- execcmd += [attrib]
- if not self.default_pbs_conf or not self.default_client_pbs_conf:
- as_script = True
- else:
- as_script = False
- if not self._is_local or as_script:
- execcmd = '\'' + " ".join(execcmd) + '\''
- else:
- execcmd = " ".join(execcmd)
- # Hooks can only be queried as a privileged user on the host where
- # the server is running, care must be taken to use the appropriate
- # path to qmgr and appropriate escaping sequences
- # VERSION INFO: no_sched_hook_event introduced in 11.3.120 only
- if sudo is None:
- if (obj_type in (HOOK, PBS_HOOK) or
- (attrib is not None and
- ('job_sort_formula' in attrib or
- 'acl_roots' in attrib or
- 'no_sched_hook_event' in attrib))):
- sudo = True
- else:
- sudo = False
- pcmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'bin', 'qmgr'),
- '-c', execcmd]
- if as_script:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- ret = self.du.run_cmd(self.hostname, pcmd, sudo=sudo, runas=runas,
- level=logging.INFOCLI, as_script=as_script,
- logerr=logerr)
- rc = ret['rc']
- # NOTE: workaround the fact that qmgr overloads the return code in
- # cases where the list returned is empty an error flag is set even
- # through there is no error. Handled here by checking if there is
- # no err and out message, in which case return code is set to 0
- if rc != 0 and (ret['out'] == [''] and ret['err'] == ['']):
- rc = 0
- if rc == 0:
- if cmd == MGR_CMD_LIST:
- bsl = self.utils.convert_to_dictlist(ret['out'], attrib,
- mergelines=True)
- self.update_attributes(obj_type, bsl)
- else:
- # Need to rework setting error, this is not thread safe
- self.last_error = ret['err']
- self.last_rc = ret['rc']
- elif runas is not None:
- _data = {'cmd': cmd, 'obj_type': obj_type, 'attrib': attrib,
- 'id': oid}
- rc = self.pbs_api_as('manager', user=runas, data=_data,
- extend=extend)
- else:
- a = self.utils.convert_to_attropl(attrib, cmd)
- c = self._connect(self.hostname)
- rc = 0
- if obj_type == SERVER and oid is None:
- oid = [self.hostname]
- if oid is None:
- # server will run strlen on id, it can not be NULL
- oid = ['']
- if cmd == MGR_CMD_LIST:
- if oid is None:
- bsl = self.status(obj_type, attrib, oid, extend)
- else:
- bsl = None
- for i in oid:
- tmpbsl = self.status(obj_type, attrib, i, extend)
- if tmpbsl is None:
- rc = 1
- else:
- if bsl is None:
- bsl = tmpbsl
- else:
- bsl += tmpbsl
- else:
- rc = 0
- if oid is None:
- rc = pbs_manager(c, cmd, obj_type, i, a, extend)
- else:
- for i in oid:
- tmprc = pbs_manager(c, cmd, obj_type, i, a, extend)
- if tmprc != 0:
- rc = tmprc
- break
- if rc == 0:
- rc = tmprc
- bs_list = []
- if cmd == MGR_CMD_DELETE and oid is not None and rc == 0:
- for i in oid:
- if obj_type == MGR_OBJ_HOOK and i in self.hooks:
- del self.hooks[i]
- if obj_type in (NODE, VNODE) and i in self.nodes:
- del self.nodes[i]
- if obj_type == MGR_OBJ_QUEUE and i in self.queues:
- del self.queues[i]
- if obj_type == MGR_OBJ_RSC and i in self.resources:
- del self.resources[i]
- if obj_type == SCHED and i in self.schedulers:
- del self.schedulers[i]
- elif cmd == MGR_CMD_SET and rc == 0 and id is not None:
- if isinstance(id, list):
- for name in id:
- tbsl = copy.deepcopy(attrib)
- tbsl['name'] = name
- bs_list.append(tbsl)
- self.update_attributes(obj_type, bs_list)
- else:
- tbsl = copy.deepcopy(attrib)
- tbsl['id'] = id
- bs_list.append(tbsl)
- self.update_attributes(obj_type, bs_list)
- elif cmd == MGR_CMD_CREATE and rc == 0:
- if isinstance(id, list):
- for name in id:
- bsl = self.status(obj_type, id=name, extend=extend)
- self.update_attributes(obj_type, bsl)
- else:
- bsl = self.status(obj_type, id=id, extend=extend)
- self.update_attributes(obj_type, bsl)
- if rc != 0:
- raise PbsManagerError(rv=False, rc=rc, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c is not None:
- self._disconnect(c)
- if expect:
- offset = None
- attrop = PTL_OR
- if obj_type in (NODE, HOST):
- obj_type = VNODE
- if obj_type in (VNODE, QUEUE):
- offset = 0.5
- if cmd in PBS_CMD_TO_OP:
- op = PBS_CMD_TO_OP[cmd]
- else:
- op = EQ
- # If scheduling is set to false then check for
- # state to be idle
- if attrib and isinstance(attrib,
- dict) and 'scheduling' in attrib.keys():
- if str(attrib['scheduling']) in PTL_FALSE:
- if obj_type == MGR_OBJ_SERVER:
- state_val = 'Idle'
- state_attr = ATTR_status
- else: # SCHED object
- state_val = 'idle'
- state_attr = 'state'
- attrib[state_attr] = state_val
- attrop = PTL_AND
- if oid is None:
- self.expect(obj_type, attrib, op=op,
- max_attempts=max_attempts,
- attrop=attrop, offset=offset)
- else:
- for i in oid:
- self.expect(obj_type, attrib, i, op=op,
- max_attempts=max_attempts,
- attrop=attrop, offset=offset)
- rc = 0 # If we've reached here then expect passed, so return 0
- return rc
- def sigjob(self, jobid=None, signal=None, extend=None, runas=None,
- logerr=True):
- """
- Send a signal to a job. Raises ``PbsSignalError`` on error.
- :param jobid: identifier of the job or list of jobs to send
- the signal to
- :type jobid: str or list
- :param signal: The signal to send to the job, see pbs_ifl.h
- :type signal: str or None
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsSignalError
- """
- prefix = 'signal on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if jobid is not None:
- if not isinstance(jobid, list):
- jobid = jobid.split(',')
- prefix += ', '.join(jobid)
- if signal is not None:
- prefix += ' with signal = ' + str(signal)
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qsig')]
- if signal is not None:
- pcmd += ['-s']
- if signal != PTL_NOARG:
- pcmd += [str(signal)]
- if jobid is not None:
- pcmd += jobid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('sigjob', jobid, runas, data=signal)
- else:
- c = self._connect(self.hostname)
- rc = 0
- for ajob in jobid:
- tmp_rc = pbs_sigjob(c, ajob, signal, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsSignalError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def msgjob(self, jobid=None, to_file=None, msg=None, extend=None,
- runas=None, logerr=True):
- """
- Send a message to a job. Raises ``PbsMessageError`` on
- error.
- :param jobid: identifier of the job or list of jobs to
- send the message to
- :type jobid: str or List
- :param msg: The message to send to the job
- :type msg: str or None
- :param to_file: one of ``MSG_ERR`` or ``MSG_OUT`` or
- ``MSG_ERR|MSG_OUT``
- :type to_file: str or None
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsMessageError
- """
- prefix = 'msgjob on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if jobid is not None:
- if not isinstance(jobid, list):
- jobid = jobid.split(',')
- prefix += ', '.join(jobid)
- if to_file is not None:
- prefix += ' with to_file = '
- if MSG_ERR == to_file:
- prefix += 'MSG_ERR'
- elif MSG_OUT == to_file:
- prefix += 'MSG_OUT'
- elif MSG_OUT | MSG_ERR == to_file:
- prefix += 'MSG_ERR|MSG_OUT'
- else:
- prefix += str(to_file)
- if msg is not None:
- prefix += ' msg = %s' % (str(msg))
- if extend is not None:
- prefix += ' extend = %s' % (str(extend))
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qmsg')]
- if to_file is not None:
- if MSG_ERR == to_file:
- pcmd += ['-E']
- elif MSG_OUT == to_file:
- pcmd += ['-O']
- elif MSG_OUT | MSG_ERR == to_file:
- pcmd += ['-E', '-O']
- else:
- pcmd += ['-' + str(to_file)]
- if msg is not None:
- pcmd += [msg]
- if jobid is not None:
- pcmd += jobid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- data = {'msg': msg, 'to_file': to_file}
- rc = self.pbs_api_as('msgjob', jobid, runas, data=data,
- extend=extend)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- for ajob in jobid:
- tmp_rc = pbs_msgjob(c, ajob, to_file, msg, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsMessageError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def alterjob(self, jobid=None, attrib=None, extend=None, runas=None,
- logerr=True):
- """
- Alter attributes associated to a job. Raises
- ``PbsAlterError`` on error.
- :param jobid: identifier of the job or list of jobs to
- operate on
- :type jobid: str or list
- :param attrib: A dictionary of attributes to set
- :type attrib: dictionary
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If False, CLI commands do not log error,
- i.e. silent mode
- :type logerr: bool
- :raises: PbsAlterError
- """
- prefix = 'alter on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if jobid is not None:
- if not isinstance(jobid, list):
- jobid = jobid.split(',')
- prefix += ', '.join(jobid)
- if attrib is not None:
- prefix += ' %s' % (str(attrib))
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'qalter')]
- if attrib is not None:
- _conf = self.default_client_pbs_conf
- pcmd += self.utils.convert_to_cli(attrib, op=IFL_ALTER,
- hostname=self.client,
- dflt_conf=_conf)
- if jobid is not None:
- pcmd += jobid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('alterjob', jobid, runas, data=attrib)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- a = self.utils.convert_to_attrl(attrib)
- rc = 0
- for ajob in jobid:
- tmp_rc = pbs_alterjob(c, ajob, a, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsAlterError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def holdjob(self, jobid=None, holdtype=None, extend=None, runas=None,
- logerr=True):
- """
- Hold a job. Raises ``PbsHoldError`` on error.
- :param jobid: identifier of the job or list of jobs to hold
- :type jobid: str or list
- :param holdtype: The type of hold to put on the job
- :type holdtype: str or None
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsHoldError
- """
- prefix = 'holdjob on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if jobid is not None:
- if not isinstance(jobid, list):
- jobid = jobid.split(',')
- prefix += ', '.join(jobid)
- if holdtype is not None:
- prefix += ' with hold_list = %s' % (holdtype)
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qhold')]
- if holdtype is not None:
- pcmd += ['-h']
- if holdtype != PTL_NOARG:
- pcmd += [holdtype]
- if jobid is not None:
- pcmd += jobid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- logerr=logerr, as_script=as_script,
- level=logging.INFOCLI)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('holdjob', jobid, runas, data=holdtype,
- logerr=logerr)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- rc = 0
- for ajob in jobid:
- tmp_rc = pbs_holdjob(c, ajob, holdtype, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsHoldError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def rlsjob(self, jobid, holdtype, extend=None, runas=None, logerr=True):
- """
- Release a job. Raises ``PbsReleaseError`` on error.
- :param jobid: job or list of jobs to release
- :type jobid: str or list
- :param holdtype: The type of hold to release on the job
- :type holdtype: str
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsReleaseError
- """
- prefix = 'release on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if jobid is not None:
- if not isinstance(jobid, list):
- jobid = jobid.split(',')
- prefix += ', '.join(jobid)
- if holdtype is not None:
- prefix += ' with hold_list = %s' % (holdtype)
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qrls')]
- if holdtype is not None:
- pcmd += ['-h']
- if holdtype != PTL_NOARG:
- pcmd += [holdtype]
- if jobid is not None:
- pcmd += jobid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('rlsjob', jobid, runas, data=holdtype)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- rc = 0
- for ajob in jobid:
- tmp_rc = pbs_rlsjob(c, ajob, holdtype, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsHoldError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def rerunjob(self, jobid=None, extend=None, runas=None, logerr=True):
- """
- Rerun a job. Raises ``PbsRerunError`` on error.
- :param jobid: job or list of jobs to release
- :type jobid: str or list
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsRerunError
- """
- prefix = 'rerun on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if jobid is not None:
- if not isinstance(jobid, list):
- jobid = jobid.split(',')
- prefix += ', '.join(jobid)
- if extend is not None:
- prefix += extend
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'qrerun')]
- if extend:
- pcmd += ['-W', extend]
- if jobid is not None:
- pcmd += jobid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('rerunjob', jobid, runas, extend=extend)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- rc = 0
- for ajob in jobid:
- tmp_rc = pbs_rerunjob(c, ajob, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsRerunError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def orderjob(self, jobid1=None, jobid2=None, extend=None, runas=None,
- logerr=True):
- """
- reorder position of ``jobid1`` and ``jobid2``. Raises
- ``PbsOrderJob`` on error.
- :param jobid1: first jobid
- :type jobid1: str or None
- :param jobid2: second jobid
- :type jobid2: str or None
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsOrderJob
- """
- prefix = 'orderjob on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- prefix += str(jobid1) + ', ' + str(jobid2)
- if extend is not None:
- prefix += ' ' + str(extend)
- self.logger.info(prefix)
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'qorder')]
- if jobid1 is not None:
- pcmd += [jobid1]
- if jobid2 is not None:
- pcmd += [jobid2]
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('orderjob', jobid1, runas, data=jobid2,
- extend=extend)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- rc = pbs_orderjob(c, jobid1, jobid2, extend)
- if rc != 0:
- raise PbsOrderError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def runjob(self, jobid=None, location=None, async=False, extend=None,
- runas=None, logerr=False):
- """
- Run a job on given nodes. Raises ``PbsRunError`` on error.
- :param jobid: job or list of jobs to run
- :type jobid: str or list
- :param location: An execvnode on which to run the job
- :type location: str or None
- :param async: If true the call will return immediately
- assuming success.
- :type async: bool
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsRunError
- """
- if async:
- prefix = 'Async run on ' + self.shortname
- else:
- prefix = 'run on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if jobid is not None:
- if not isinstance(jobid, list):
- jobid = jobid.split(',')
- prefix += ', '.join(jobid)
- if location is not None:
- prefix += ' with location = %s' % (location)
- self.logger.info(prefix)
- if self.has_diag:
- return 0
- c = None
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qrun')]
- if async:
- pcmd += ['-a']
- if location is not None:
- pcmd += ['-H']
- if location != PTL_NOARG:
- pcmd += [location]
- if jobid:
- pcmd += jobid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as(
- 'runjob', jobid, runas, data=location, extend=extend)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- rc = 0
- for ajob in jobid:
- if async:
- tmp_rc = pbs_asyrunjob(c, ajob, location, extend)
- else:
- tmp_rc = pbs_runjob(c, ajob, location, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsRunError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def movejob(self, jobid=None, destination=None, extend=None, runas=None,
- logerr=True):
- """
- Move a job or list of job ids to a given destination queue.
- Raises ``PbsMoveError`` on error.
- :param jobid: A job or list of job ids to move
- :type jobid: str or list
- :param destination: The destination queue@server
- :type destination: str or None
- :param extend: extend options
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsMoveError
- """
- prefix = 'movejob on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if jobid is not None:
- if not isinstance(jobid, list):
- jobid = jobid.split(',')
- prefix += ', '.join(jobid)
- if destination is not None:
- prefix += ' destination = %s' % (destination)
- self.logger.info(prefix)
- c = None
- rc = 0
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qmove')]
- if destination is not None:
- pcmd += [destination]
- if jobid is not None:
- pcmd += jobid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- logerr=logerr, as_script=as_script,
- level=logging.INFOCLI)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('movejob', jobid, runas, data=destination,
- extend=extend)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- for ajob in jobid:
- tmp_rc = pbs_movejob(c, ajob, destination, extend)
- if tmp_rc != 0:
- rc = tmp_rc
- if rc != 0:
- raise PbsMoveError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- if c:
- self._disconnect(c)
- return rc
- def qterm(self, manner=None, extend=None, server_name=None, runas=None,
- logerr=True):
- """
- Terminate the ``pbs_server`` daemon
- :param manner: one of ``(SHUT_IMMEDIATE | SHUT_DELAY |
- SHUT_QUICK)`` and can be\
- combined with SHUT_WHO_SCHED, SHUT_WHO_MOM,
- SHUT_WHO_SECDRY, \
- SHUT_WHO_IDLESECDRY, SHUT_WHO_SECDONLY. \
- :param extend: extend options
- :param server_name: name of the pbs server
- :type server_name: str or None
- :param runas: run as user
- :type runas: str or None
- :param logerr: If True (default) logs run_cmd errors
- :type logerr: bool
- :raises: PbsQtermError
- """
- prefix = 'terminate ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': with manner '
- attrs = manner
- if attrs is None:
- prefix += "None "
- elif isinstance(attrs, str):
- prefix += attrs
- else:
- if ((attrs & SHUT_QUICK) == SHUT_QUICK):
- prefix += "quick "
- if ((attrs & SHUT_IMMEDIATE) == SHUT_IMMEDIATE):
- prefix += "immediate "
- if ((attrs & SHUT_DELAY) == SHUT_DELAY):
- prefix += "delay "
- if ((attrs & SHUT_WHO_SCHED) == SHUT_WHO_SCHED):
- prefix += "schedular "
- if ((attrs & SHUT_WHO_MOM) == SHUT_WHO_MOM):
- prefix += "mom "
- if ((attrs & SHUT_WHO_SECDRY) == SHUT_WHO_SECDRY):
- prefix += "secondary server "
- if ((attrs & SHUT_WHO_IDLESECDRY) == SHUT_WHO_IDLESECDRY):
- prefix += "idle secondary "
- if ((attrs & SHUT_WHO_SECDONLY) == SHUT_WHO_SECDONLY):
- prefix += "shoutdown secondary only "
- self.logger.info(prefix)
- if self.has_diag:
- return 0
- c = None
- rc = 0
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin', 'qterm')]
- _conf = self.default_client_pbs_conf
- pcmd += self.utils.convert_to_cli(manner, op=IFL_TERMINATE,
- hostname=self.hostname,
- dflt_conf=_conf)
- if server_name is not None:
- pcmd += [server_name]
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- level=logging.INFOCLI, as_script=as_script)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = rc
- elif runas is not None:
- attrs = {'manner': manner, 'server_name': server_name}
- rc = self.pbs_api_as('terminate', None, runas, data=attrs,
- extend=extend)
- else:
- if server_name is None:
- server_name = self.hostname
- c = self._connect(self.hostname)
- rc = pbs_terminate(c, manner, extend)
- if rc != 0:
- raise PbsQtermError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c, force=True)
- if c:
- self._disconnect(c, force=True)
- return rc
- teminate = qterm
- def geterrmsg(self):
- """
- Get the error message
- """
- mode = self.get_op_mode()
- if mode == PTL_CLI:
- return self.last_error
- elif self._conn is not None and self._conn >= 0:
- m = pbs_geterrmsg(self._conn)
- if m is not None:
- m = m.split('\n')
- return m
- #
- # End IFL Wrappers
- #
- def qdisable(self, queue=None, runas=None, logerr=True):
- """
- Disable queue. ``CLI`` mode only
- :param queue: The name of the queue or list of queue to
- disable
- :type queue: str or list
- :param runas: Optional name of user to run command as
- :type runas: str or None
- :param logerr: Set to False ot disable logging command
- errors.Defaults to True.
- :type logerr: bool
- :raises: PbsQdisableError
- """
- prefix = 'qdisable on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if queue is not None:
- if not isinstance(queue, list):
- queue = queue.split(',')
- prefix += ', '.join(queue)
- self.logger.info(prefix)
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'qdisable')]
- if queue is not None:
- pcmd += queue
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = ret['rc']
- if self.last_rc != 0:
- raise PbsQdisableError(rc=self.last_rc, rv=False,
- msg=self.last_error)
- else:
- _msg = 'qdisable: currently not supported in API mode'
- raise PbsQdisableError(rv=False, rc=1, msg=_msg)
- def qenable(self, queue=None, runas=None, logerr=True):
- """
- Enable queue. ``CLI`` mode only
- :param queue: The name of the queue or list of queue to
- enable
- :type queue: str or list
- :param runas: Optional name of user to run command as
- :type runas: str or None
- :param logerr: Set to False ot disable logging command
- errors.Defaults to True.
- :type logerr: bool
- :raises: PbsQenableError
- """
- prefix = 'qenable on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if queue is not None:
- if not isinstance(queue, list):
- queue = queue.split(',')
- prefix += ', '.join(queue)
- self.logger.info(prefix)
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'qenable')]
- if queue is not None:
- pcmd += queue
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = ret['rc']
- if self.last_rc != 0:
- raise PbsQenableError(rc=self.last_rc, rv=False,
- msg=self.last_error)
- else:
- _msg = 'qenable: currently not supported in API mode'
- raise PbsQenableError(rv=False, rc=1, msg=_msg)
- def qstart(self, queue=None, runas=None, logerr=True):
- """
- Start queue. ``CLI`` mode only
- :param queue: The name of the queue or list of queue
- to start
- :type queue: str or list
- :param runas: Optional name of user to run command as
- :type runas: str or None
- :param logerr: Set to False ot disable logging command
- errors.Defaults to True.
- :type logerr: bool
- :raises: PbsQstartError
- """
- prefix = 'qstart on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if queue is not None:
- if not isinstance(queue, list):
- queue = queue.split(',')
- prefix += ', '.join(queue)
- self.logger.info(prefix)
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'qstart')]
- if queue is not None:
- pcmd += queue
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = ret['rc']
- if self.last_rc != 0:
- raise PbsQstartError(rc=self.last_rc, rv=False,
- msg=self.last_error)
- else:
- _msg = 'qstart: currently not supported in API mode'
- raise PbsQstartError(rv=False, rc=1, msg=_msg)
- def qstop(self, queue=None, runas=None, logerr=True):
- """
- Stop queue. ``CLI`` mode only
- :param queue: The name of the queue or list of queue to stop
- :type queue: str or list
- :param runas: Optional name of user to run command as
- :type runas: str or None
- :param logerr: Set to False ot disable logging command errors.
- Defaults to True.
- :type logerr: bool
- :raises: PbsQstopError
- """
- prefix = 'qstop on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': '
- if queue is not None:
- if not isinstance(queue, list):
- queue = queue.split(',')
- prefix += ', '.join(queue)
- self.logger.info(prefix)
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'qstop')]
- if queue is not None:
- pcmd += queue
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- if ret['err'] != ['']:
- self.last_error = ret['err']
- self.last_rc = ret['rc']
- if self.last_rc != 0:
- raise PbsQstopError(rc=self.last_rc, rv=False,
- msg=self.last_error)
- else:
- _msg = 'qstop: currently not supported in API mode'
- raise PbsQstopError(rv=False, rc=1, msg=_msg)
- def parse_resources(self):
- """
- Parse server resources as defined in the resourcedef file
- Populates instance variable self.resources
- :returns: The resources as a dictionary
- """
- if not self.has_diag:
- self.manager(MGR_CMD_LIST, RSC)
- return self.resources
- def remove_resource(self, name):
- """
- Remove an entry from resourcedef
- :param name: The name of the resource to remove
- :type name: str
- :param restart: Whether to restart the server or not.
- Applicable to update_mode 'file'
- operations only.
- :param update_mode: one of 'file' or 'auto' (the default).
- If 'file', updates the resourcedef file
- only and will not use the qmgr
- operations on resources introduced in
- 12.3. If 'auto', will automatically
- handle the update on resourcedef or
- using qmgr based on the version of the
- Server.
- """
- self.parse_resources()
- if not self.has_diag:
- if name in self.resources:
- self.manager(MGR_CMD_DELETE, RSC, id=name)
- def add_resource(self, name, type=None, flag=None):
- """
- Define a server resource
- :param name: The name of the resource to add to the
- resourcedef file
- :type name: str
- :param type: The type of the resource, one of string,
- long, boolean, float
- :param flag: The target of the resource, one of n, h, q,
- or none
- :type flag: str or None
- :param restart: Whether to restart the server after adding
- a resource.Applicable to update_mode 'file'
- operations only.
- :param update_mode: one of 'file' or 'auto' (the default).
- If 'file', updates the resourcedef file
- only and will not use the qmgr
- operations on resources introduced in
- 12.3. If 'auto', will automatically
- handle the update on resourcedef or
- using qmgr based on the version of the
- Server.
- :returns: True on success False on error
- """
- rv = self.parse_resources()
- if rv is None:
- return False
- resource_exists = False
- if name in self.resources:
- msg = [self.logprefix + "resource " + name]
- if type:
- msg += ["type: " + type]
- if flag:
- msg += ["flag: " + flag]
- msg += [" already defined"]
- self.logger.info(" ".join(msg))
- (t, f) = (self.resources[name].type, self.resources[name].flag)
- if type == t and flag == f:
- return True
- self.logger.info("resource: redefining resource " + name +
- " type: " + str(type) + " and flag: " + str(flag))
- del self.resources[name]
- resource_exists = True
- r = Resource(name, type, flag)
- self.resources[name] = r
- a = {}
- if type:
- a['type'] = type
- if flag:
- a['flag'] = flag
- if resource_exists:
- self.manager(MGR_CMD_SET, RSC, a, id=name)
- else:
- self.manager(MGR_CMD_CREATE, RSC, a, id=name)
- return True
- def write_resourcedef(self, resources=None, filename=None, restart=True):
- """
- Write into resource def file
- :param resources: PBS resources
- :type resources: dictionary
- :param filename: resourcedef file name
- :type filename: str or None
- """
- if resources is None:
- resources = self.resources
- if isinstance(resources, Resource):
- resources = {resources.name: resources}
- fn = self.du.create_temp_file()
- with open(fn, 'w+') as f:
- for r in resources.values():
- f.write(r.attributes['id'])
- if r.attributes['type'] is not None:
- f.write(' type=' + r.attributes['type'])
- if r.attributes['flag'] is not None:
- f.write(' flag=' + r.attributes['flag'])
- f.write('\n')
- if filename is None:
- dest = os.path.join(self.pbs_conf['PBS_HOME'], 'server_priv',
- 'resourcedef')
- else:
- dest = filename
- self.du.run_copy(self.hostname, fn, dest, sudo=True,
- preserve_permission=False)
- os.remove(fn)
- if restart:
- return self.restart()
- return True
- def parse_resourcedef(self, file=None):
- """
- Parse an arbitrary resource definition file passed as
- input and return a dictionary of resources
- :param file: resource definition file
- :type file: str or None
- :returns: Dictionary of resource
- :raises: PbsResourceError
- """
- if file is None:
- file = os.path.join(self.pbs_conf['PBS_HOME'], 'server_priv',
- 'resourcedef')
- ret = self.du.cat(self.hostname, file, logerr=False, sudo=True)
- if ret['rc'] != 0 or len(ret['out']) == 0:
- # Most probable error is that file does not exist, we'll let it
- # be created
- return {}
- resources = {}
- lines = ret['out']
- try:
- for l in lines:
- l = l.strip()
- if l == '' or l.startswith('#'):
- continue
- name = None
- rtype = None
- flag = None
- res = l.split()
- e0 = res[0]
- if len(res) > 1:
- e1 = res[1].split('=')
- else:
- e1 = None
- if len(res) > 2:
- e2 = res[2].split('=')
- else:
- e2 = None
- if e1 is not None and e1[0] == 'type':
- rtype = e1[1]
- elif e2 is not None and e2[0] == 'type':
- rtype = e2[1]
- if e1 is not None and e1[0] == 'flag':
- flag = e1[0]
- elif e2 is not None and e2[0] == 'flag':
- flag = e2[1]
- name = e0
- r = Resource(name, rtype, flag)
- resources[name] = r
- except:
- raise PbsResourceError(rc=1, rv=False,
- msg="error in parse_resources")
- return resources
- def pbs_api_as(self, cmd=None, obj=None, user=None, **kwargs):
- """
- Generic handler to run an ``API`` call impersonating
- a given user.This method is only used for impersonation
- over the ``API`` because ``CLI`` impersonation takes place
- through the generic ``DshUtils`` run_cmd mechanism.
- :param cmd: PBS command
- :type cmd: str or None
- :param user: PBS user or current user
- :type user: str or None
- :raises: eval
- """
- fn = None
- objid = None
- _data = None
- if user is None:
- user = self.du.get_current_user()
- else:
- # user may be a PbsUser object, cast it to string for the remainder
- # of the function
- user = str(user)
- if cmd == 'submit':
- if obj is None:
- return None
- _data = copy.copy(obj)
- # the following attributes cause problems 'pickling',
- # since they are not needed we unset them
- _data.attrl = None
- _data.attropl = None
- _data.logger = None
- _data.utils = None
- elif cmd in ('alterjob', 'holdjob', 'sigjob', 'msgjob', 'rlsjob',
- 'rerunjob', 'orderjob', 'runjob', 'movejob',
- 'select', 'delete', 'status', 'manager', 'terminate',
- 'deljob', 'delresv', 'alterresv'):
- objid = obj
- if 'data' in kwargs:
- _data = kwargs['data']
- if _data is not None:
- fn = self.du.create_temp_file()
- with open(fn, 'w+b') as tmpfile:
- cPickle.dump(_data, tmpfile)
- os.chmod(fn, 0755)
- if self._is_local:
- os.chdir(tempfile.gettempdir())
- else:
- self.du.run_copy(self.hostname, fn, fn)
- if not self._is_local:
- p_env = '"import os; print os.environ[\'PTL_EXEC\']"'
- ret = self.du.run_cmd(self.hostname, ['python', '-c', p_env],
- logerr=False)
- if ret['out']:
- runcmd = [os.path.join(ret['out'][0], 'pbs_as')]
- else:
- runcmd = ['pbs_as']
- elif 'PTL_EXEC' in os.environ:
- runcmd = [os.path.join(os.environ['PTL_EXEC'], 'pbs_as')]
- else:
- runcmd = ['pbs_as']
- runcmd += ['-c', cmd, '-u', user]
- if objid is not None:
- runcmd += ['-o']
- if isinstance(objid, list):
- runcmd += [','.join(objid)]
- else:
- runcmd += [objid]
- if fn is not None:
- runcmd += ['-f', fn]
- if 'hostname' in kwargs:
- hostname = kwargs['hostname']
- else:
- hostname = self.hostname
- runcmd += ['-s', hostname]
- if 'extend' in kwargs and kwargs['extend'] is not None:
- runcmd += ['-e', kwargs['extend']]
- ret = self.du.run_cmd(self.hostname, runcmd, logerr=False, runas=user)
- out = ret['out']
- if ret['err']:
- if cmd in CMD_ERROR_MAP:
- m = CMD_ERROR_MAP[cmd]
- if m in ret['err'][0]:
- if fn is not None:
- os.remove(fn)
- if not self._is_local:
- self.du.rm(self.hostname, fn)
- raise eval(str(ret['err'][0]))
- self.logger.debug('err: ' + str(ret['err']))
- if fn is not None:
- os.remove(fn)
- if not self._is_local:
- self.du.rm(self.hostname, fn)
- if cmd == 'submit':
- if out:
- return out[0].strip()
- else:
- return None
- elif cmd in ('alterjob', 'holdjob', 'sigjob', 'msgjob', 'rlsjob',
- 'rerunjob', 'orderjob', 'runjob', 'movejob', 'delete',
- 'terminate', 'alterresv'):
- if ret['out']:
- return int(ret['out'][0])
- else:
- return 1
- elif cmd in ('manager', 'select', 'status'):
- return eval(out[0])
- def alterresv(self, resvid, attrib, extend=None, runas=None,
- logerr=True):
- """
- Alter attributes associated to a reservation. Raises
- ``PbsResvAlterError`` on error.
- :param resvid: identifier of the reservation.
- :type resvid: str.
- :param attrib: A dictionary of attributes to set.
- :type attrib: dictionary.
- :param extend: extend options.
- :param runas: run as user.
- :type runas: str or None.
- :param logerr: If False, CLI commands do not log error,
- i.e. silent mode.
- :type logerr: bool.
- :raises: PbsResvAlterError.
- """
- prefix = 'reservation alter on ' + self.shortname
- if runas is not None:
- prefix += ' as ' + str(runas)
- prefix += ': ' + resvid
- if attrib is not None:
- prefix += ' %s' % (str(attrib))
- self.logger.info(prefix)
- c = None
- resvid = resvid.split()
- if self.get_op_mode() == PTL_CLI:
- pcmd = [os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'pbs_ralter')]
- if attrib is not None:
- _conf = self.default_client_pbs_conf
- pcmd += self.utils.convert_to_cli(attrib, op=IFL_RALTER,
- hostname=self.client,
- dflt_conf=_conf)
- pcmd += resvid
- if not self.default_client_pbs_conf:
- pcmd = ['PBS_CONF_FILE=' + self.client_pbs_conf_file] + pcmd
- as_script = True
- else:
- as_script = False
- ret = self.du.run_cmd(self.client, pcmd, runas=runas,
- as_script=as_script, level=logging.INFOCLI,
- logerr=logerr)
- rc = ret['rc']
- if ret['err'] != ['']:
- self.last_error = ret['err']
- if ret['out'] != ['']:
- self.last_out = ret['out']
- self.last_rc = rc
- elif runas is not None:
- rc = self.pbs_api_as('alterresv', resvid, runas, data=attrib)
- else:
- c = self._connect(self.hostname)
- if c < 0:
- return c
- a = self.utils.convert_to_attrl(attrib)
- rc = pbs_modify_resv(c, resvid, a, extend)
- if rc != 0:
- raise PbsResvAlterError(rc=rc, rv=False, msg=self.geterrmsg(),
- post=self._disconnect, conn=c)
- else:
- return rc
- if c:
- self._disconnect(c)
- def expect(self, obj_type, attrib=None, id=None, op=EQ, attrop=PTL_OR,
- attempt=0, max_attempts=None, interval=None, count=None,
- extend=None, offset=0, runas=None, level=logging.INFO,
- msg=None):
- """
- expect an attribute to match a given value as per an
- operation.
- :param obj_type: The type of object to query, JOB, SERVER,
- SCHEDULER, QUEUE NODE
- :type obj_type: str
- :param attrib: Attributes to query, can be a string, a list,
- or a dict
- :type attrib: str or list or dictionary
- :param id: The id of the object to act upon
- :param op: An operation to perform on the queried data,
- e.g., EQ, SET, LT,..
- :param attrop: Operation on multiple attributes, either
- PTL_AND, PTL_OR when an PTL_AND is used, only
- batch objects having all matches are
- returned, otherwise an OR is applied
- :param attempt: The number of times this function has been
- called
- :type attempt: int
- :param max_attempts: The maximum number of attempts to
- perform.C{param_max_attempts}: 5
- :type max_attempts: int or None
- :param interval: The interval time btween attempts.
- C{param_interval}: 1s
- :param count: If True, attrib will be accumulated using
- function counter
- :type count: bool
- :param extend: passed to the stat call
- :param offset: the time to wait before the initial check.
- Defaults to 0.
- :type offset: int
- :param runas: query as a given user. Defaults to current
- user
- :type runas: str or None
- :param msg: Message from last call of this function, this
- message will be used while raising
- PtlExpectError.
- :type msg: str or None
- :returns: True if attributes are as expected
- :raises: PtlExpectError if attributes are not as expected
- """
- if attempt == 0 and offset > 0:
- self.logger.log(level, self.logprefix + 'expect offset set to ' +
- str(offset))
- time.sleep(offset)
- if attrib is None:
- attrib = {}
- if ATTR_version in attrib and max_attempts is None:
- max_attempts = 3
- if max_attempts is None:
- max_attempts = int(self.ptl_conf['expect_max_attempts'])
- if interval is None:
- interval = self.ptl_conf['expect_interval']
- if attempt >= max_attempts:
- _msg = "expected on " + self.logprefix + msg
- raise PtlExpectError(rc=1, rv=False, msg=_msg)
- if obj_type == SERVER and id is None:
- id = self.hostname
- if isinstance(attrib, str):
- attrib = {attrib: ''}
- elif isinstance(attrib, list):
- d = {}
- for l in attrib:
- d[l] = ''
- attrib = d
- # Add check for substate=42 for jobstate=R, if not added explicitly.
- if obj_type == JOB:
- add_attribs = {'substate': False}
- substate = False
- for k, v in attrib.items():
- if k == 'job_state' and ((isinstance(v, tuple) and
- 'R' in v[-1]) or v == 'R'):
- add_attribs['substate'] = 42
- elif k == 'job_state=R':
- add_attribs['substate=42'] = v
- elif 'substate' in k:
- substate = True
- if add_attribs['substate'] and not substate:
- attrib['substate'] = add_attribs['substate']
- attrop = PTL_AND
- del add_attribs, substate
- prefix = 'expect on ' + self.logprefix
- msg = []
- attrs_to_ignore = []
- for k, v in attrib.items():
- args = None
- if isinstance(v, tuple):
- operator = v[0]
- if len(v) > 2:
- args = v[2:]
- val = v[1]
- else:
- operator = op
- val = v
- if operator not in PTL_OP_TO_STR:
- self.logger.log(level, "Operator not supported by expect(), "
- "cannot verify change in " + str(k))
- attrs_to_ignore.append(k)
- continue
- msg += [k, PTL_OP_TO_STR[operator].strip()]
- if callable(val):
- msg += ['callable(' + val.__name__ + ')']
- if args is not None:
- msg.extend(map(lambda x: str(x), args))
- else:
- msg += [str(val)]
- msg += [PTL_ATTROP_TO_STR[attrop]]
- # Delete the attributes that we cannot verify
- for k in attrs_to_ignore:
- del(attrib[k])
- if attrs_to_ignore and len(attrib) < 1 and op == SET:
- return True
- # remove the last converted PTL_ATTROP_TO_STR
- if len(msg) > 1:
- msg = msg[:-1]
- if len(attrib) == 0:
- msg += [PTL_OP_TO_STR[op]]
- msg += [PBS_OBJ_MAP[obj_type]]
- if id is not None:
- msg += [str(id)]
- if attempt > 0:
- msg += ['attempt:', str(attempt + 1)]
- # Default count to True if the attribute contains an '=' in its name
- # for example 'job_state=R' implies that a count of job_state is needed
- if count is None and self.utils.operator_in_attribute(attrib):
- count = True
- if count:
- newattr = self.utils.convert_attributes_by_op(attrib)
- if len(newattr) == 0:
- newattr = attrib
- statlist = [self.counter(obj_type, newattr, id, extend, op=op,
- attrop=attrop, level=logging.DEBUG,
- runas=runas)]
- else:
- try:
- statlist = self.status(obj_type, attrib, id=id,
- level=logging.DEBUG, extend=extend,
- runas=runas, logerr=False)
- except PbsStatusError:
- statlist = []
- if (statlist is None or len(statlist) == 0 or
- statlist[0] is None or len(statlist[0]) == 0):
- if op == UNSET or list(set(attrib.values())) == [0]:
- self.logger.log(level, prefix + " ".join(msg) + ' ... OK')
- return True
- else:
- time.sleep(interval)
- msg = " no data for " + " ".join(msg)
- self.logger.log(level, prefix + msg)
- return self.expect(obj_type, attrib, id, op, attrop,
- attempt + 1, max_attempts, interval, count,
- extend, level=level, msg=msg)
- if attrib is None:
- time.sleep(interval)
- return self.expect(obj_type, attrib, id, op, attrop, attempt + 1,
- max_attempts, interval, count, extend,
- runas=runas, level=level, msg=" ".join(msg))
- for k, v in attrib.items():
- varargs = None
- if isinstance(v, tuple):
- op = v[0]
- if len(v) > 2:
- varargs = v[2:]
- v = v[1]
- for stat in statlist:
- if k not in stat:
- if op == UNSET:
- continue
- # Sometimes users provide the wrong case for attributes
- # Convert to lowercase and compare
- attrs_lower = {
- ks.lower(): [ks, vs] for ks, vs in stat.iteritems()}
- k_lower = k.lower()
- if k_lower not in attrs_lower:
- self.logger.error("Attribute %s not found" % k)
- return False
- stat_v = attrs_lower[k_lower][1]
- stat_k = attrs_lower[k_lower][0]
- else:
- stat_v = stat[k]
- stat_k = k
- if stat_k == ATTR_version:
- m = self.version_tag.match(stat_v)
- if m:
- stat_v = m.group('version')
- else:
- time.sleep(interval)
- return self.expect(obj_type, attrib, id, op, attrop,
- attempt + 1, max_attempts, interval,
- count, extend, runas=runas,
- level=level, msg=" ".join(msg))
- # functions/methods are invoked and their return value
- # used on expect
- if callable(v):
- if varargs is not None:
- rv = v(stat_v, *varargs)
- else:
- rv = v(stat_v)
- if isinstance(rv, bool):
- if op == NOT:
- if not rv:
- continue
- if rv:
- continue
- else:
- v = rv
- stat_v = self.utils.decode_value(stat_v)
- v = self.utils.decode_value(str(v))
- if stat_k == ATTR_version:
- stat_v = LooseVersion(str(stat_v))
- v = LooseVersion(str(v))
- if op == EQ and stat_v == v:
- continue
- elif op == SET and count and stat_v == v:
- continue
- elif op == SET and count in (False, None):
- continue
- elif op == NE and stat_v != v:
- continue
- elif op == LT:
- if stat_v < v:
- continue
- elif op == GT:
- if stat_v > v:
- continue
- elif op == LE:
- if stat_v <= v:
- continue
- elif op == GE:
- if stat_v >= v:
- continue
- elif op == MATCH_RE:
- if re.search(str(v), str(stat_v)):
- continue
- elif op == MATCH:
- if str(stat_v).find(str(v)) != -1:
- continue
- msg += [' got: ' + stat_k + ' = ' + str(stat_v)]
- self.logger.info(prefix + " ".join(msg))
- time.sleep(interval)
- # run custom actions defined for this object type
- if self.actions:
- for act_obj in self.actions.get_actions_by_type(obj_type):
- if act_obj.enabled:
- act_obj.action(self, obj_type, attrib, id, op,
- attrop)
- return self.expect(obj_type, attrib, id, op, attrop,
- attempt + 1, max_attempts, interval, count,
- extend, level=level, msg=" ".join(msg))
- self.logger.log(level, prefix + " ".join(msg) + ' ... OK')
- return True
- def is_history_enabled(self):
- """
- Short-hand method to return the value of job_history_enable
- """
- a = ATTR_JobHistoryEnable
- attrs = self.status(SERVER, level=logging.DEBUG)[0]
- if ((a in attrs.keys()) and attrs[a] == 'True'):
- return True
- return False
- def cleanup_jobs(self, extend=None, runas=None):
- """
- Helper function to delete all jobs.
- By default this method will determine whether
- job_history_enable is on and will cleanup all history
- jobs. Specifying an extend parameter could override
- this behavior.
- :param runas: Clean the job as
- :type runas: str or None
- """
- delete_xt = 'force'
- select_xt = None
- if self.is_history_enabled():
- delete_xt += 'deletehist'
- select_xt = 'x'
- job_ids = self.select(extend=select_xt)
- if len(job_ids) > 0:
- try:
- self.deljob(id=job_ids, extend=delete_xt, runas=runas,
- wait=True)
- except:
- pass
- rv = self.expect(JOB, {'job_state': 0}, count=True, op=SET)
- if not rv:
- return self.cleanup_jobs(extend=extend, runas=runas)
- return rv
- def cleanup_reservations(self, extend=None, runas=None):
- """
- Helper function to delete all reservations
- """
- reservations = self.status(RESV, level=logging.DEBUG)
- while reservations is not None and len(reservations) != 0:
- resvs = [r['id'] for r in reservations]
- if len(resvs) > 0:
- try:
- self.delresv(resvs, logerr=False, runas=runas)
- except:
- pass
- reservations = self.status(RESV, level=logging.DEBUG)
- def cleanup_jobs_and_reservations(self, extend='forcedeletehist'):
- """
- Helper function to delete all jobs and reservations
- :param extend: Optional extend parameter that is passed
- to delete. It defaults to 'deletehist' which
- is used in qdel and pbs_deljob() to force
- delete all jobs, including history jobs
- :param extend: str
- """
- rv = self.cleanup_jobs(extend)
- self.cleanup_reservations()
- return rv
- def update_attributes(self, obj_type, bs):
- """
- Populate objects from batch status data
- """
- if bs is None:
- return
- for binfo in bs:
- if 'id' not in binfo:
- continue
- id = binfo['id']
- obj = None
- if obj_type == JOB:
- if ATTR_owner in binfo:
- user = binfo[ATTR_owner].split('@')[0]
- else:
- user = None
- if id in self.jobs:
- self.jobs[id].attributes.update(binfo)
- if self.jobs[id].username != user:
- self.jobs[id].username = user
- else:
- self.jobs[id] = Job(user, binfo)
- obj = self.jobs[id]
- elif obj_type in (VNODE, NODE):
- if id in self.nodes:
- self.nodes[id].attributes.update(binfo)
- else:
- self.nodes[id] = MoM(id, binfo, diagmap={NODE: None},
- server=self)
- obj = self.nodes[id]
- elif obj_type == SERVER:
- self.attributes.update(binfo)
- obj = self
- elif obj_type == QUEUE:
- if id in self.queues:
- self.queues[id].attributes.update(binfo)
- else:
- self.queues[id] = Queue(id, binfo, server=self)
- obj = self.queues[id]
- elif obj_type == RESV:
- if id in self.reservations:
- self.reservations[id].attributes.update(binfo)
- else:
- self.reservations[id] = Reservation(id, binfo)
- obj = self.reservations[id]
- elif obj_type == HOOK:
- if id in self.hooks:
- self.hooks[id].attributes.update(binfo)
- else:
- self.hooks[id] = Hook(id, binfo, server=self)
- obj = self.hooks[id]
- elif obj_type == PBS_HOOK:
- if id in self.pbshooks:
- self.pbshooks[id].attributes.update(binfo)
- else:
- self.pbshooks[id] = Hook(id, binfo, server=self)
- obj = self.pbshooks[id]
- elif obj_type == SCHED:
- if id in self.schedulers:
- self.schedulers[id].attributes.update(binfo)
- if 'sched_priv' in binfo:
- self.schedulers[id].setup_sched_priv(
- binfo['sched_priv'])
- else:
- if 'sched_host' not in binfo:
- hostname = self.hostname
- else:
- hostname = binfo['sched_host']
- if SCHED in self.diagmap:
- diag = self.diag
- diagmap = self.diagmap
- else:
- diag = None
- diagmap = {}
- spriv = None
- if 'sched_priv' in binfo:
- spriv = binfo['sched_priv']
- self.schedulers[id] = Scheduler(hostname=hostname,
- server=self,
- diag=diag,
- diagmap=diagmap,
- id=id,
- sched_priv=spriv)
- self.schedulers[id].attributes.update(binfo)
- obj = self.schedulers[id]
- elif obj_type == RSC:
- if id in self.resources:
- self.resources[id].attributes.update(binfo)
- else:
- rtype = None
- rflag = None
- if 'type' in binfo:
- rtype = binfo['type']
- if 'flag' in binfo:
- rflag = binfo['flag']
- self.resources[id] = Resource(id, rtype, rflag)
- if obj is not None:
- self.utils.update_attributes_list(obj)
- obj.__dict__.update(binfo)
- def counter(self, obj_type=None, attrib=None, id=None, extend=None,
- op=None, attrop=None, bslist=None, level=logging.INFO,
- idonly=True, grandtotal=False, db_access=None, runas=None,
- resolve_indirectness=False):
- """
- Accumulate properties set on an object. For example, to
- count number of free nodes:
- ``server.counter(VNODE,{'state':'free'})``
- :param obj_type: The type of object to query, one of the
- * objects
- :param attrib: Attributes to query, can be a string, a
- list, a dictionary
- :type attrib: str or list or dictionary
- :param id: The id of the object to act upon
- :param extend: The extended parameter to pass to the stat
- call
- :param op: The operation used to match attrib to what is
- queried. SET or None
- :type op: str or None
- :param attrop: Operation on multiple attributes, either
- PTL_AND, PTL_OR
- :param bslist: Optional, use a batch status dict list
- instead of an obj_type
- :param idonly: if true, return the name/id of the matching
- objects
- :type idonly: bool
- :param db_access: credentials to access db, either a path
- to file or dictionary
- :type db_access: str or dictionary
- :param runas: run as user
- :type runas: str or None
- """
- self.logit('counter: ', obj_type, attrib, id, level=level)
- return self._filter(obj_type, attrib, id, extend, op, attrop, bslist,
- PTL_COUNTER, idonly, grandtotal, db_access,
- runas=runas,
- resolve_indirectness=resolve_indirectness)
- def filter(self, obj_type=None, attrib=None, id=None, extend=None, op=None,
- attrop=None, bslist=None, idonly=True, grandtotal=False,
- db_access=None, runas=None, resolve_indirectness=False):
- """
- Filter objects by properties. For example, to filter all
- free nodes:``server.filter(VNODE,{'state':'free'})``
- For each attribute queried, if idonly is True, a list of
- matching object names is returned; if idonly is False, then
- the value of each attribute queried is returned.
- This is unlike Python's built-in 'filter' that returns a
- subset of objects matching from a pool of objects. The
- Python filtering mechanism remains very useful in some
- situations and should be used programmatically to achieve
- desired filtering goals that can not be met easily with
- PTL's filter method.
- :param obj_type: The type of object to query, one of the
- * objects
- :param attrib: Attributes to query, can be a string, a
- list, a dictionary
- :type attrib: str or list or dictionary
- :param id: The id of the object to act upon
- :param extend: The extended parameter to pass to the stat
- call
- :param op: The operation used to match attrib to what is
- queried. SET or None
- :type op: str or None
- :param bslist: Optional, use a batch status dict list
- instead of an obj_type
- :type bslist: List or None
- :param idonly: if true, return the name/id of the matching
- objects
- :type idonly: bool
- :param db_access: credentials to access db, either path to
- file or dictionary
- :type db_access: str or dictionary
- :param runas: run as user
- :type runas: str or None
- """
- self.logit('filter: ', obj_type, attrib, id)
- return self._filter(obj_type, attrib, id, extend, op, attrop, bslist,
- PTL_FILTER, idonly, db_access, runas=runas,
- resolve_indirectness=resolve_indirectness)
- def _filter(self, obj_type=None, attrib=None, id=None, extend=None,
- op=None, attrop=None, bslist=None, mode=PTL_COUNTER,
- idonly=True, grandtotal=False, db_access=None, runas=None,
- resolve_indirectness=False):
- if bslist is None:
- try:
- _a = resolve_indirectness
- tmp_bsl = self.status(obj_type, attrib, id,
- level=logging.DEBUG, extend=extend,
- db_access=db_access, runas=runas,
- resolve_indirectness=_a)
- del _a
- except PbsStatusError:
- return None
- bslist = self.utils.filter_batch_status(tmp_bsl, attrib)
- del tmp_bsl
- if bslist is None:
- return None
- if isinstance(attrib, str):
- attrib = attrib.split(',')
- total = {}
- for bs in bslist:
- if isinstance(attrib, list):
- # when filtering on multiple values, ensure that they are
- # all present on the object, otherwise skip
- if attrop == PTL_AND:
- match = True
- for k in attrib:
- if k not in bs:
- match = False
- if not match:
- continue
- for a in attrib:
- if a in bs:
- if op == SET:
- k = a
- else:
- # Since this is a list of attributes, no operator
- # was provided so we settle on "equal"
- k = a + '=' + str(bs[a])
- if mode == PTL_COUNTER:
- amt = 1
- if grandtotal:
- amt = self.utils.decode_value(bs[a])
- if not isinstance(amt, (int, float)):
- amt = 1
- if a in total:
- total[a] += amt
- else:
- total[a] = amt
- else:
- if k in total:
- total[k] += amt
- else:
- total[k] = amt
- elif mode == PTL_FILTER:
- if k in total:
- if idonly:
- total[k].append(bs['id'])
- else:
- total[k].append(bs)
- else:
- if idonly:
- total[k] = [bs['id']]
- else:
- total[k] = [bs]
- else:
- self.logger.error("Unhandled mode " + str(mode))
- return None
- elif isinstance(attrib, dict):
- tmptotal = {} # The running count that will be used for total
- # when filtering on multiple values, ensure that they are
- # all present on the object, otherwise skip
- match = True
- for k, v in attrib.items():
- if k not in bs:
- match = False
- if attrop == PTL_AND:
- break
- else:
- continue
- amt = self.utils.decode_value(bs[k])
- if isinstance(v, tuple):
- op = v[0]
- val = self.utils.decode_value(v[1])
- elif op == SET:
- val = None
- pass
- else:
- op = EQ
- val = self.utils.decode_value(v)
- if ((op == LT and amt < val) or
- (op == LE and amt <= val) or
- (op == EQ and amt == val) or
- (op == GE and amt >= val) or
- (op == GT and amt > val) or
- (op == NE and amt != val) or
- (op == MATCH and str(amt).find(str(val)) != -1) or
- (op == MATCH_RE and
- re.search(str(val), str(amt))) or
- (op == SET)):
- # There is a match, proceed to track the attribute
- self._filter_helper(bs, k, val, amt, op, mode,
- tmptotal, idonly, grandtotal)
- elif attrop == PTL_AND:
- match = False
- if mode == PTL_COUNTER:
- # requesting specific key/value pairs should result
- # in 0 available elements
- tmptotal[str(k) + PTL_OP_TO_STR[op] + str(val)] = 0
- break
- elif mode == PTL_COUNTER:
- tmptotal[str(k) + PTL_OP_TO_STR[op] + str(val)] = 0
- if attrop != PTL_AND or (attrop == PTL_AND and match):
- for k, v in tmptotal.items():
- if k not in total:
- total[k] = v
- else:
- total[k] += v
- return total
- def _filter_helper(self, bs, k, v, amt, op, mode, total, idonly,
- grandtotal):
- # default operation to '='
- if op is None or op not in PTL_OP_TO_STR:
- op = '='
- op_str = PTL_OP_TO_STR[op]
- if op == SET:
- # override PTL_OP_TO_STR fro SET operations
- op_str = ''
- v = ''
- ky = k + op_str + str(v)
- if mode == PTL_COUNTER:
- incr = 1
- if grandtotal:
- if not isinstance(amt, (int, float)):
- incr = 1
- else:
- incr = amt
- if ky in total:
- total[ky] += incr
- else:
- total[ky] = incr
- elif mode == PTL_FILTER:
- if ky in total:
- if idonly:
- total[ky].append(bs['id'])
- else:
- total[ky].append(bs)
- else:
- if idonly:
- total[ky] = [bs['id']]
- else:
- total[ky] = [bs]
- def logit(self, msg, obj_type, attrib, id, level=logging.INFO):
- """
- Generic logging routine for ``IFL`` commands
- :param msg: The message to log
- :type msg: str
- :param obj_type: object type, i.e *
- :param attrib: attributes to log
- :param id: name of object to log
- :type id: str or list
- :param level: log level, defaults to ``INFO``
- """
- s = []
- if self.logger is not None:
- if obj_type is None:
- obj_type = MGR_OBJ_NONE
- s = [msg + PBS_OBJ_MAP[obj_type]]
- if id:
- if isinstance(id, list):
- s += [' ' + ",".join(id)]
- else:
- s += [' ' + str(id)]
- if attrib:
- s += [' ' + str(attrib)]
- self.logger.log(level, "".join(s))
- def equivalence_classes(self, obj_type=None, attrib={}, bslist=None,
- op=RESOURCES_AVAILABLE, show_zero_resources=True,
- db_access=None, resolve_indirectness=False):
- """
- :param obj_type: PBS Object to query, one of *
- :param attrib: attributes to build equivalence classes
- out of.
- :type attrib: dictionary
- :param bslist: Optional, list of dictionary representation
- of a batch status
- :type bslist: List
- :param op: set to RESOURCES_AVAILABLE uses the dynamic
- amount of resources available, i.e., available -
- assigned, otherwise uses static amount of
- resources available
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- """
- if attrib is None:
- attrib = {}
- if len(attrib) == 0 and obj_type is not None:
- if obj_type in (VNODE, NODE):
- attrib = ['resources_available.ncpus',
- 'resources_available.mem', 'state']
- elif obj_type == JOB:
- attrib = ['Resource_List.select',
- 'queue', 'array_indices_submitted']
- elif obj_type == RESV:
- attrib = ['Resource_List.select']
- else:
- return {}
- if bslist is None and obj_type is not None:
- # To get the resources_assigned we must stat the entire object so
- # bypass the specific attributes that would filter out assigned
- if op == RESOURCES_AVAILABLE:
- bslist = self.status(obj_type, None, level=logging.DEBUG,
- db_access=db_access,
- resolve_indirectness=resolve_indirectness)
- else:
- bslist = self.status(obj_type, attrib, level=logging.DEBUG,
- db_access=db_access,
- resolve_indirectness=resolve_indirectness)
- if bslist is None or len(bslist) == 0:
- return {}
- # automatically convert an objectlist into a batch status dict list
- # for ease of use.
- if not isinstance(bslist[0], dict):
- bslist = self.utils.objlist_to_dictlist(bslist)
- if isinstance(attrib, str):
- attrib = attrib.split(',')
- self.logger.debug("building equivalence class")
- equiv = {}
- for bs in bslist:
- cls = ()
- skip_cls = False
- # attrs will be part of the EquivClass object
- attrs = {}
- # Filter the batch attributes by the attribs requested
- for a in attrib:
- if a in bs:
- amt = self.utils.decode_value(bs[a])
- if a.startswith('resources_available.'):
- val = a.replace('resources_available.', '')
- if (op == RESOURCES_AVAILABLE and
- 'resources_assigned.' + val in bs):
- amt = (int(amt) - int(self.utils.decode_value(
- bs['resources_assigned.' + val])))
- # this case where amt goes negative is not a bug, it
- # may happen when computing whats_available due to the
- # fact that the computation is subtractive, it does
- # add back resources when jobs/reservations end but
- # is only concerned with what is available now for
- # a given duration, that is why in the case where
- # amount goes negative we set it to 0
- if amt < 0:
- amt = 0
- # TODO: not a failproof way to catch a memory type
- # but PbsTypeSize should return the right value if
- # it fails to parse it as a valid memory value
- if a.endswith('mem'):
- try:
- amt = PbsTypeSize().encode(amt)
- except:
- # we guessed the type incorrectly
- pass
- else:
- val = a
- if amt == 0 and not show_zero_resources:
- skip_cls = True
- break
- # Build the key of the equivalence class
- cls += (val + '=' + str(amt),)
- attrs[val] = amt
- # Now that we are done with this object, add it to an equiv class
- if len(cls) > 0 and not skip_cls:
- if cls in equiv:
- equiv[cls].add_entity(bs['id'])
- else:
- equiv[cls] = EquivClass(cls, attrs, [bs['id']])
- return equiv.values()
- def show_equivalence_classes(self, eq=None, obj_type=None, attrib={},
- bslist=None, op=RESOURCES_AVAILABLE,
- show_zero_resources=True, db_access=None,
- resolve_indirectness=False):
- """
- helper function to show the equivalence classes
- :param eq: equivalence classes as compute by
- equivalence_classes see equivalence_classes
- for remaining parameters description
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- """
- if eq is None:
- equiv = self.equivalence_classes(obj_type, attrib, bslist, op,
- show_zero_resources, db_access,
- resolve_indirectness)
- else:
- equiv = eq
- equiv = sorted(equiv, key=lambda e: len(e.entities))
- for e in equiv:
- # e.show()
- print str(e)
- def whats_available(self, attrib=None, jobs=None, resvs=None, nodes=None):
- """
- Returns what's available as a list of node equivalence
- classes listed by availability over time.
- :param attrib: attributes to consider
- :type attrib: List
- :param jobs: jobs to consider, if None, jobs are queried
- locally
- :param resvs: reservations to consider, if None, they are
- queried locally
- :param nodes: nodes to consider, if None, they are queried
- locally
- """
- if attrib is None:
- attrib = ['resources_available.ncpus',
- 'resources_available.mem', 'state']
- if resvs is None:
- self.status(RESV)
- resvs = self.reservations
- if jobs is None:
- self.status(JOB)
- jobs = self.jobs
- if nodes is None:
- self.status(NODE)
- nodes = self.nodes
- nodes_id = nodes.keys()
- avail_nodes_by_time = {}
- def alloc_resource(self, node, resources):
- # helper function. Must work on a scratch copy of nodes otherwise
- # resources_available will get corrupted
- for rsc, value in resources.items():
- if isinstance(value, int) or value.isdigit():
- avail = node.attributes['resources_available.' + rsc]
- nvalue = int(avail) - int(value)
- node.attributes['resources_available.' + rsc] = nvalue
- # Account for reservations
- for resv in resvs.values():
- resvnodes = resv.execvnode('resv_nodes')
- if resvnodes:
- starttime = self.utils.convert_stime_to_seconds(
- resv.attributes['reserve_start'])
- for node in resvnodes:
- for n, resc in node.items():
- tm = int(starttime) - int(self.ctime)
- if tm < 0 or n not in nodes_id:
- continue
- if tm not in avail_nodes_by_time:
- avail_nodes_by_time[tm] = []
- if nodes[n].attributes['sharing'] in ('default_excl',
- 'force_excl'):
- avail_nodes_by_time[tm].append(nodes[n])
- try:
- nodes_id.remove(n)
- except:
- pass
- else:
- ncopy = copy.copy(nodes[n])
- ncopy.attributes = copy.deepcopy(
- nodes[n].attributes)
- avail_nodes_by_time[tm].append(ncopy)
- self.alloc_resource(nodes[n], resc)
- # go on to look at the calendar of scheduled jobs to run and set
- # the node availability according to when the job is estimated to
- # start on the node
- for job in self.jobs.values():
- if (job.attributes['job_state'] != 'R' and
- 'estimated.exec_vnode' in job.attributes):
- estimatednodes = job.execvnode('estimated.exec_vnode')
- if estimatednodes:
- st = job.attributes['estimated.start_time']
- # Tweak for nas format of estimated time that has
- # num seconds from epoch followed by datetime
- if st.split()[0].isdigit():
- starttime = st.split()[0]
- else:
- starttime = self.utils.convert_stime_to_seconds(st)
- for node in estimatednodes:
- for n, resc in node.items():
- tm = int(starttime) - int(self.ctime)
- if (tm < 0 or n not in nodes_id or
- nodes[n].state != 'free'):
- continue
- if tm not in avail_nodes_by_time:
- avail_nodes_by_time[tm] = []
- if (nodes[n].attributes['sharing'] in
- ('default_excl', 'force_excl')):
- avail_nodes_by_time[tm].append(nodes[n])
- try:
- nodes_id.remove(n)
- except:
- pass
- else:
- ncopy = copy.copy(nodes[n])
- ncopy.attributes = copy.deepcopy(
- nodes[n].attributes)
- avail_nodes_by_time[tm].append(ncopy)
- self.alloc_resource(nodes[n], resc)
- # remaining nodes are free "forever"
- for node in nodes_id:
- if self.nodes[node].state == 'free':
- if 'infinity' not in avail_nodes_by_time:
- avail_nodes_by_time['infinity'] = [nodes[node]]
- else:
- avail_nodes_by_time['infinity'].append(nodes[node])
- # if there is a dedicated time, move the availaility time up to that
- # time as necessary
- if self.schedulers[self.dflt_sched_name] is None:
- self.schedulers[self.dflt_sched_name] = Scheduler(server=self)
- self.schedulers[self.dflt_sched_name].parse_dedicated_time()
- if self.schedulers[self.dflt_sched_name].dedicated_time:
- dedtime = self.schedulers[
- self.dflt_sched_name].dedicated_time[0]['from'] - int(
- self.ctime)
- if dedtime <= int(time.time()):
- dedtime = None
- else:
- dedtime = None
- # finally, build the equivalence classes off of the nodes availability
- # over time
- self.logger.debug("Building equivalence classes")
- whazzup = {}
- if 'state' in attrib:
- attrib.remove('state')
- for tm, nds in avail_nodes_by_time.items():
- equiv = self.equivalence_classes(VNODE, attrib, bslist=nds,
- show_zero_resources=False)
- if dedtime and (tm > dedtime or tm == 'infinity'):
- tm = dedtime
- if tm != 'infinity':
- tm = str(datetime.timedelta(seconds=int(tm)))
- whazzup[tm] = equiv
- return whazzup
- def show_whats_available(self, wa=None, attrib=None, jobs=None,
- resvs=None, nodes=None):
- """
- helper function to show availability as computed by
- whats_available
- :param wa: a dictionary of available attributes. see
- whats_available for a\
- description of the remaining parameters
- :type wa: Dictionary
- """
- if wa is None:
- wa = self.whats_available(attrib, jobs, resvs, nodes)
- if len(wa) > 0:
- print "%24s\t%s" % ("Duration of availability", "Resources")
- print "-------------------------\t----------"
- swa = sorted(wa.items(), key=lambda x: x[0])
- for (k, eq_classes) in swa:
- for eq_cl in eq_classes:
- print "%24s\t%s" % (str(k), str(eq_cl))
- def utilization(self, resources=None, nodes=None, jobs=None, entity={}):
- """
- Return utilization of consumable resources on a set of
- nodes
- :param nodes: A list of dictionary of nodes on which to
- compute utilization.Defaults to nodes
- resulting from a stat call to the current
- server.
- :type nodes: List
- :param resources: comma-separated list of resources to
- compute utilization on. The name of the
- resource is for example, ncpus or mem
- :type resources: List
- :param entity: An optional dictionary of entities to
- compute utilization of,
- ``e.g. {'user':u1, 'group':g1, 'project'=p1}``
- :type entity: Dictionary
- The utilization is returned as a dictionary of percentage
- utilization for each resource.
- Non-consumable resources are silently ignored.
- """
- if nodes is None:
- nodes = self.status(NODE)
- if jobs is None:
- jobs = self.status(JOB)
- if resources is None:
- rescs = ['ncpus', 'mem']
- else:
- rescs = resources
- utilization = {}
- resavail = {}
- resassigned = {}
- usednodes = 0
- totnodes = 0
- nodes_set = set()
- for res in rescs:
- resavail[res] = 0
- resassigned[res] = 0
- # If an entity is specified utilization must be collected from the
- # Jobs usage, otherwise we can get the information directly from
- # the nodes.
- if len(entity) > 0 and jobs is not None:
- for job in jobs:
- if 'job_state' in job and job['job_state'] != 'R':
- continue
- entity_match = True
- for k, v in entity.items():
- if k not in job or job[k] != v:
- entity_match = False
- break
- if entity_match:
- for res in rescs:
- r = 'Resource_List.' + res
- if r in job:
- tmpr = int(self.utils.decode_value(job[r]))
- resassigned[res] += tmpr
- if 'exec_host' in job:
- hosts = ResourceResv.get_hosts(job['exec_host'])
- nodes_set |= set(hosts)
- for node in nodes:
- # skip nodes in non-schedulable state
- nstate = node['state']
- if ('down' in nstate or 'unavailable' in nstate or
- 'unknown' in nstate or 'Stale' in nstate):
- continue
- totnodes += 1
- # If an entity utilization was requested, all used nodes were
- # already filtered into the nodes_set specific to that entity, we
- # simply add them up. If no entity was requested, it suffices to
- # have the node have a jobs attribute to count it towards total
- # used nodes
- if len(entity) > 0:
- if node['id'] in nodes_set:
- usednodes += 1
- elif 'jobs' in node:
- usednodes += 1
- for res in rescs:
- avail = 'resources_available.' + res
- if avail in node:
- val = self.utils.decode_value(node[avail])
- if isinstance(val, int):
- resavail[res] += val
- # When entity matching all resources assigned are
- # accounted for by the job usage
- if len(entity) == 0:
- assigned = 'resources_assigned.' + res
- if assigned in node:
- val = self.utils.decode_value(node[assigned])
- if isinstance(val, int):
- resassigned[res] += val
- for res in rescs:
- if res in resavail:
- if res in resassigned:
- if resavail[res] > 0:
- utilization[res] = [resassigned[res], resavail[res]]
- # Only report nodes utilization if no specific resources were requested
- if resources is None:
- utilization['nodes'] = [usednodes, totnodes]
- return utilization
- def create_vnodes(self, name=None, attrib=None, num=1, mom=None,
- additive=False, sharednode=True, restart=True,
- delall=True, natvnode=None, usenatvnode=False,
- attrfunc=None, fname=None, vnodes_per_host=1,
- createnode=True, expect=True):
- """
- helper function to create vnodes.
- :param name: prefix name of the vnode(s) to create
- :type name: str or None
- :param attrib: attributes to assign to each node
- :param num: the number of vnodes to create. Defaults to 1
- :type num: int
- :param mom: the MoM object on which the vnode definition is
- to be inserted
- :param additive: If True, vnodes are added to the existing
- vnode defs.Defaults to False.
- :type additive: bool
- :param sharednode: If True, all vnodes will share the same
- host.Defaults to True.
- :type sharednode: bool
- :param restart: If True the MoM will be restarted.
- :type restart: bool
- :param delall: If True delete all server nodes prior to
- inserting vnodes
- :type delall: bool
- :param natvnode: name of the natural vnode.i.e. The node
- name in qmgr -c "create node <name>"
- :type natvnode: str or None
- :param usenatvnode: count the natural vnode as an
- allocatable node.
- :type usenatvnode: bool
- :param attrfunc: an attribute=value function generator,
- see create_vnode_def
- :param fname: optional name of the vnode def file
- :type fname: str or None
- :param vnodes_per_host: number of vnodes per host
- :type vnodes_per_host: int
- :param createnode: whether to create the node via manage or
- not. Defaults to True
- :type createnode: bool
- :param expect: whether to expect attributes to be set or
- not. Defaults to True
- :type expect: bool
- :returns: True on success and False otherwise
- """
- if mom is None or name is None or attrib is None:
- self.logger.error("name, attributes, and mom object are required")
- return False
- if natvnode is None:
- natvnode = mom.shortname
- if delall:
- try:
- rv = self.manager(MGR_CMD_DELETE, NODE, None, "")
- if rv != 0:
- return False
- except PbsManagerError:
- pass
- vdef = mom.create_vnode_def(name, attrib, num, sharednode,
- usenatvnode=usenatvnode, attrfunc=attrfunc,
- vnodes_per_host=vnodes_per_host)
- mom.insert_vnode_def(vdef, fname=fname, additive=additive,
- restart=restart)
- new_vnodelist = []
- if usenatvnode:
- new_vnodelist.append(natvnode)
- num_check = num - 1
- else:
- num_check = num
- for i in range(num_check):
- new_vnodelist.append("%s[%s]" % (name, i))
- if createnode:
- try:
- statm = self.status(NODE, id=natvnode)
- except:
- statm = []
- if len(statm) >= 1:
- _m = 'Mom %s already exists, not creating' % (natvnode)
- self.logger.info(_m)
- else:
- if mom.pbs_conf and 'PBS_MOM_SERVICE_PORT' in mom.pbs_conf:
- m_attr = {'port': mom.pbs_conf['PBS_MOM_SERVICE_PORT']}
- else:
- m_attr = None
- self.manager(MGR_CMD_CREATE, NODE, m_attr, natvnode)
- # only expect if vnodes were added rather than the nat vnode modified
- if expect and num > 0:
- attrs = {'state': 'free'}
- attrs.update(attrib)
- for vn in new_vnodelist:
- self.expect(VNODE, attrs, id=vn)
- return True
- def create_moms(self, name=None, attrib=None, num=1, delall=True,
- createnode=True, conf_prefix='pbs.conf_m',
- home_prefix='pbs_m', momhosts=None, init_port=15011,
- step_port=2):
- """
- Create MoM configurations and optionall add them to the
- server. Unique ``pbs.conf`` files are defined and created
- on each hosts on which MoMs are to be created.
- :param name: Optional prefix name of the nodes to create.
- Defaults to the name of the MoM host.
- :type name: str or None
- :param attrib: Optional node attributes to assign to the
- MoM.
- :param num: Number of MoMs to create
- :type num: int
- :param delall: Whether to delete all nodes on the server.
- Defaults to True.
- :type delall: bool
- :param createnode: Whether to create the nodes and add them
- to the server.Defaults to True.
- :type createnode: bool
- :param conf_prefix: The prefix of the PBS conf file.Defaults
- to pbs.conf_m
- :type conf_prefix: str
- :param home_prefix: The prefix of the PBS_HOME directory.
- Defaults to pbs_m
- :type home_prefix: str
- :param momhosts: A list of hosts on which to deploy num
- MoMs.
- :type momhosts: List
- :param init_port: The initial port number to start assigning
- ``PBS_MOM_SERIVCE_PORT to.
- Default 15011``.
- :type init_port: int
- :param step_port: The increments at which ports are
- allocated. Defaults to 2.
- :type step_port: int
- .. note:: Since PBS requires that
- PBS_MANAGER_SERVICE_PORT = PBS_MOM_SERVICE_PORT+1
- The step number must be greater or equal to 2.
- """
- if not self.isUp():
- logging.error("An up and running PBS server on " + self.hostname +
- " is required")
- return False
- if delall:
- try:
- rc = self.manager(MGR_CMD_DELETE, NODE, None, "")
- except PbsManagerError, e:
- rc = e.rc
- if rc:
- if len(self.status(NODE)) > 0:
- self.logger.error("create_moms: Error deleting all nodes")
- return False
- pi = PBSInitServices()
- if momhosts is None:
- momhosts = [self.hostname]
- if attrib is None:
- attrib = {}
- error = False
- for hostname in momhosts:
- _pconf = self.du.parse_pbs_config(hostname)
- if 'PBS_HOME' in _pconf:
- _hp = _pconf['PBS_HOME']
- if _hp.endswith('/'):
- _hp = _hp[:-1]
- _hp = os.path.dirname(_hp)
- else:
- _hp = '/var/spool'
- _np_conf = _pconf
- _np_conf['PBS_START_SERVER'] = '0'
- _np_conf['PBS_START_SCHED'] = '0'
- _np_conf['PBS_START_MOM'] = '1'
- for i in xrange(0, num * step_port, step_port):
- _np = os.path.join(_hp, home_prefix + str(i))
- _n_pbsconf = os.path.join('/etc', conf_prefix + str(i))
- _np_conf['PBS_HOME'] = _np
- port = init_port + i
- _np_conf['PBS_MOM_SERVICE_PORT'] = str(port)
- _np_conf['PBS_MANAGER_SERVICE_PORT'] = str(port + 1)
- self.du.set_pbs_config(hostname, fout=_n_pbsconf,
- confs=_np_conf)
- pi.initd(hostname, conf_file=_n_pbsconf, op='start')
- m = MoM(hostname, pbsconf_file=_n_pbsconf)
- if m.isUp():
- m.stop()
- if hostname != self.hostname:
- m.add_config({'$clienthost': self.hostname})
- try:
- m.start()
- except PbsServiceError:
- # The service failed to start
- self.logger.error("Service failed to start using port " +
- str(port) + "...skipping")
- self.du.rm(hostname, _n_pbsconf)
- continue
- if createnode:
- attrib['Mom'] = hostname
- attrib['port'] = port
- if name is None:
- name = hostname.split('.')[0]
- _n = name + '-' + str(i)
- rc = self.manager(MGR_CMD_CREATE, NODE, attrib, id=_n)
- if rc != 0:
- self.logger.error("error creating node " + _n)
- error = True
- if error:
- return False
- return True
- def create_hook(self, name, attrs):
- """
- Helper function to create a hook by name.
- :param name: The name of the hook to create
- :type name: str
- :param attrs: The attributes to create the hook with.
- :type attrs: str
- :returns: False if hook already exists
- :raises: PbsManagerError, otherwise return True.
- """
- hooks = self.status(HOOK)
- if ((hooks is None or len(hooks) == 0) or
- (name not in map(lambda x: x['id'], hooks))):
- self.manager(MGR_CMD_CREATE, HOOK, None, name)
- else:
- self.logger.error('hook named ' + name + ' exists')
- return False
- self.manager(MGR_CMD_SET, HOOK, attrs, id=name, expect=True)
- return True
- def import_hook(self, name, body):
- """
- Helper function to import hook body into hook by name.
- The hook must have been created prior to calling this
- function.
- :param name: The name of the hook to import body to
- :type name: str
- :param body: The body of the hook as a string.
- :type body: str
- :returns: True on success.
- :raises: PbsManagerError
- """
- fn = self.du.create_temp_file(body=body)
- if not self._is_local:
- tmpdir = self.du.get_tempdir(self.hostname)
- rfile = os.path.join(tmpdir, os.path.basename(fn))
- self.du.run_copy(self.hostname, fn, rfile)
- else:
- rfile = fn
- a = {'content-type': 'application/x-python',
- 'content-encoding': 'default',
- 'input-file': rfile}
- self.manager(MGR_CMD_IMPORT, HOOK, a, name)
- os.remove(rfile)
- if not self._is_local:
- self.du.rm(self.hostname, rfile)
- self.logger.info('server ' + self.shortname +
- ': imported hook body\n---\n' + body + '---')
- return True
- def create_import_hook(self, name, attrs=None, body=None, overwrite=True):
- """
- Helper function to create a hook, import content into it,
- set the event and enable it.
- :param name: The name of the hook to create
- :type name: str
- :param attrs: The attributes to create the hook with.
- Event and Enabled are mandatory. No defaults.
- :type attrs: str
- :param body: The hook body as a string
- :type body: str
- :param overwrite: If True, if a hook of the same name
- already exists, bypass its creation.
- Defaults to True
- :returns: True on success and False otherwise
- """
- # Check for log messages 20 seconds earlier, to account for
- # server and mom system time differences
- t = int(time.time()) - 20
- if 'event' not in attrs:
- self.logger.error('attrs must specify at least an event and key')
- return False
- hook_exists = False
- hooks = self.status(HOOK)
- for h in hooks:
- if h['id'] == name:
- hook_exists = True
- if not hook_exists or not overwrite:
- rv = self.create_hook(name, attrs)
- if not rv:
- return False
- else:
- if attrs is None:
- attrs = {'enabled': 'true'}
- rc = self.manager(MGR_CMD_SET, HOOK, attrs, id=name)
- if rc != 0:
- return False
- # In 12.0 A MoM hook must be enabled and the event set prior to
- # importing, otherwise the MoM does not get the hook content
- ret = self.import_hook(name, body)
- # In case of mom hooks, make sure that the hook related files
- # are successfully copied to the MoM
- try:
- if 'exec' in attrs['event']:
- hook_py = name + '.PY'
- hook_hk = name + '.HK'
- pyfile = os.path.join(self.pbs_conf['PBS_HOME'],
- "server_priv", "hooks", hook_py)
- hfile = os.path.join(self.pbs_conf['PBS_HOME'],
- "server_priv", "hooks", hook_hk)
- logmsg = hook_py + ";copy hook-related file request received"
- cmd = os.path.join(self.client_conf['PBS_EXEC'], 'bin',
- 'pbsnodes') + ' -a'
- cmd_out = self.du.run_cmd(self.hostname, cmd, sudo=True)
- if cmd_out['rc'] == 0:
- for i in cmd_out['out']:
- if re.match(r'\s+Mom = ', i):
- mom_names = i.split(' = ')[1].split(',')
- for m in mom_names:
- if m in self.moms:
- self.log_match(
- "successfully sent hook file %s to %s"
- % (hfile, m), interval=1)
- self.log_match(
- "successfully sent hook file %s to %s"
- % (pyfile, m), interval=1)
- self.moms[m].log_match(logmsg, starttime=t)
- else:
- return False
- except PtlLogMatchError:
- return False
- return ret
- def evaluate_formula(self, jobid=None, formula=None, full=True,
- include_running_jobs=False, exclude_subjobs=True):
- """
- Evaluate the job sort formula
- :param jobid: If set, evaluate the formula for the given
- jobid, if not set,formula is evaluated for
- all jobs in state Q
- :type jobid: str or None
- :param formula: If set use the given formula. If not set,
- the server's formula, if any, is used
- :param full: If True, returns a dictionary of job
- identifiers as keys and the evaluated formula
- as values. Returns None if no formula is used.
- Each job id formula is returned as a tuple
- (s,e) where s is the formula expression
- associated to the job and e is the evaluated
- numeric value of that expression, for example,
- if job_sort_formula is ncpus + mem
- a job requesting 2 cpus and 100kb of memory
- would return ('2 + 100', 102). If False, if
- a jobid is specified, return the integer
- value of the evaluated formula.
- :type full: bool
- :param include_running_jobs: If True, reports formula
- value of running jobs.
- Defaults to False.
- :type include_running_jobs: bool
- :param exclude_subjobs: If True, only report formula of
- parent job array
- :type exclude_subjobs: bool
- """
- _f_builtins = ['queue_priority', 'job_priority', 'eligible_time',
- 'fair_share_perc']
- if formula is None:
- d = self.status(SERVER, 'job_sort_formula')
- if len(d) > 0 and 'job_sort_formula' in d[0]:
- formula = d[0]['job_sort_formula']
- else:
- return None
- template_formula = self.utils._make_template_formula(formula)
- # to split up the formula into keywords, first convert all possible
- # operators into spaces and split the string.
- # TODO: The list of operators may need to be expanded
- T = string.maketrans('()%+*/-', ' ' * 7)
- fres = string.translate(formula, T).split()
- if jobid:
- d = self.status(JOB, id=jobid, extend='t')
- else:
- d = self.status(JOB, extend='t')
- ret = {}
- for job in d:
- if not include_running_jobs and job['job_state'] != 'Q':
- continue
- f_value = {}
- # initialize the formula values to 0
- for res in fres:
- f_value[res] = 0
- if 'queue_priority' in fres:
- queue = self.status(JOB, 'queue', id=job['id'])[0]['queue']
- d = self.status(QUEUE, 'Priority', id=queue)
- if d and 'Priority' in d[0]:
- qprio = int(d[0]['Priority'])
- f_value['queue_priority'] = qprio
- else:
- continue
- if 'job_priority' in fres:
- if 'Priority' in job:
- jprio = int(job['Priority'])
- f_value['job_priority'] = jprio
- else:
- continue
- if 'eligible_time' in fres:
- if 'eligible_time' in job:
- f_value['eligible_time'] = self.utils.convert_duration(
- job['eligible_time'])
- if 'fair_share_perc' in fres:
- if self.schedulers[self.dflt_sched_name] is None:
- self.schedulers[self.dflt_sched_name] = Scheduler(
- server=self)
- if 'fairshare_entity' in self.schedulers[
- self.dflt_sched_name
- ].sched_config:
- entity = self.schedulers[
- self.dflt_sched_name
- ].sched_config['fairshare_entity']
- else:
- self.logger.error(self.logprefix +
- ' no fairshare entity in sched config')
- continue
- if entity not in job:
- self.logger.error(self.logprefix +
- ' job does not have property ' + entity)
- continue
- try:
- fs_info = self.schedulers[
- self.dflt_sched_name
- ].query_fairshare(
- name=job[entity])
- if fs_info is not None and 'TREEROOT' in fs_info.perc:
- f_value['fair_share_perc'] = \
- (fs_info.perc['TREEROOT'] / 100)
- except PbsFairshareError:
- f_value['fair_share_perc'] = 0
- for job_res, val in job.items():
- val = self.utils.decode_value(val)
- if job_res.startswith('Resource_List.'):
- job_res = job_res.replace('Resource_List.', '')
- if job_res in fres and job_res not in _f_builtins:
- f_value[job_res] = val
- tf = string.Template(template_formula)
- tfstr = tf.safe_substitute(f_value)
- if (jobid is not None or not exclude_subjobs or
- (exclude_subjobs and not self.utils.is_subjob(job['id']))):
- ret[job['id']] = (tfstr, eval(tfstr))
- if not full and jobid is not None and jobid in ret:
- return ret[job['id']][1]
- return ret
- def _parse_limits(self, container=None, dictlist=None, id=None,
- db_access=None):
- """
- Helper function to parse limits syntax on a given
- container.
- :param container: The PBS object to query, one of ``QUEUE``
- or ``SERVER``.Metascheduling node group
- limits are not yet queri-able
- :type container: str or None
- :param dictlist: A list of dictionaries off of a batch
- status
- :type diclist: List
- :param id: Optional id of the object to query
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- """
- if container is None:
- self.logger.error('parse_limits expect container to be set')
- return {}
- if dictlist is None:
- d = self.status(container, db_access=db_access)
- else:
- d = dictlist
- if not d:
- return {}
- limits = {}
- for obj in d:
- # filter the id here instead of during the stat call so that
- # we can call a full stat once rather than one stat per object
- if id is not None and obj['id'] != id:
- continue
- for k, v in obj.items():
- if k.startswith('max_run'):
- v = v.split(',')
- for rval in v:
- rval = rval.strip("'")
- l = self.utils.parse_fgc_limit(k + '=' + rval)
- if l is None:
- self.logger.error("Couldn't parse limit: " +
- k + str(rval))
- continue
- (lim_type, resource, etype, ename, value) = l
- if (etype, ename) not in self.entities:
- entity = Entity(etype, ename)
- self.entities[(etype, ename)] = entity
- else:
- entity = self.entities[(etype, ename)]
- lim = Limit(lim_type, resource, entity, value,
- container, obj['id'])
- if container in limits:
- limits[container].append(lim)
- else:
- limits[container] = [lim]
- entity.set_limit(lim)
- return limits
- def parse_server_limits(self, server=None, db_access=None):
- """
- Parse all server limits
- :param server: list of dictionary of server data
- :type server: List
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- """
- return self._parse_limits(SERVER, server, db_access=db_access)
- def parse_queue_limits(self, queues=None, id=None, db_access=None):
- """
- Parse queue limits
- :param queues: list of dictionary of queue data
- :type queues: List
- :param id: The id of the queue to parse limit for. If None,
- all queue limits are parsed
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- """
- return self._parse_limits(QUEUE, queues, id=id, db_access=db_access)
- def parse_all_limits(self, server=None, queues=None, db_access=None):
- """
- Parse all server and queue limits
- :param server: list of dictionary of server data
- :type server: List
- :param queues: list of dictionary of queue data
- :type queues: List
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- """
- if hasattr(self, 'limits'):
- del self.limits
- slim = self.parse_server_limits(server, db_access=db_access)
- qlim = self.parse_queue_limits(queues, id=None, db_access=db_access)
- self.limits = dict(slim.items() + qlim.items())
- del slim
- del qlim
- return self.limits
- def limits_info(self, etype=None, ename=None, server=None, queues=None,
- jobs=None, db_access=None, over=False):
- """
- Collect limit information for each entity on which a
- ``server/queue`` limit is applied.
- :param etype: entity type, one of u, g, p, o
- :type etype: str or None
- :param ename: entity name
- :type ename: str or None
- :param server: optional list of dictionary representation
- of server object
- :type server: List
- :param queues: optional list of dictionary representation
- of queues object
- :type queues: List
- :param jobs: optional list of dictionary representation of
- jobs object
- :type jobs: List
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- :param over: If True, show only entities that are over their
- limit.Default is False.
- :type over: bool
- :returns: A list of dictionary similar to that returned by
- a converted batch_status object, i.e., can be
- displayed using the Utils.show method
- """
- def create_linfo(lim, entity_type, id, used):
- """
- Create limit information
- :param lim: Limit to apply
- :param entity_type: Type of entity
- """
- tmp = {}
- tmp['id'] = entity_type + ':' + id
- c = [PBS_OBJ_MAP[lim.container]]
- if lim.container_id:
- c += [':', lim.container_id]
- tmp['container'] = "".join(c)
- s = [str(lim.limit_type)]
- if lim.resource:
- s += ['.', lim.resource]
- tmp['limit_type'] = "".join(s)
- tmp['usage/limit'] = "".join([str(used), '/', str(lim.value)])
- tmp['remainder'] = int(lim.value) - int(used)
- return tmp
- def calc_usage(jobs, attr, name=None, resource=None):
- """
- Calculate the usage for the entity
- :param attr: Job attribute
- :param name: Entity name
- :type name: str or None
- :param resource: PBS resource
- :type resource: str or None
- :returns: The usage
- """
- usage = {}
- # initialize usage of the named entity
- if name is not None and name not in ('PBS_GENERIC', 'PBS_ALL'):
- usage[name] = 0
- for j in jobs:
- entity = j[attr]
- if entity not in usage:
- if resource:
- usage[entity] = int(
- self.utils.decode_value(
- j['Resource_List.' + resource]))
- else:
- usage[entity] = 1
- else:
- if resource:
- usage[entity] += int(
- self.utils.decode_value(
- j['Resource_List.' + resource]))
- else:
- usage[entity] += 1
- return usage
- self.parse_all_limits(server, queues, db_access)
- entities_p = self.entities.values()
- linfo = []
- cache = {}
- if jobs is None:
- jobs = self.status(JOB)
- for entity in sorted(entities_p, key=lambda e: e.name):
- for lim in entity.limits:
- _t = entity.type
- # skip non-matching entity types. We can't skip the entity
- # name due to proper handling of the PBS_GENERIC limits
- # we also can't skip overall limits
- if (_t != 'o') and (etype is not None and etype != _t):
- continue
- _n = entity.name
- a = {}
- if lim.container == QUEUE and lim.container_id is not None:
- a['queue'] = (EQ, lim.container_id)
- if lim.resource:
- resource = 'Resource_List.' + lim.resource
- a[resource] = (GT, 0)
- a['job_state'] = (EQ, 'R')
- a['substate'] = (EQ, 42)
- if etype == 'u' and ename is not None:
- a['euser'] = (EQ, ename)
- else:
- a['euser'] = (SET, '')
- if etype == 'g' and ename is not None:
- a['egroup'] = (EQ, ename)
- else:
- a['egroup'] = (SET, '')
- if etype == 'p' and ename is not None:
- a['project'] = (EQ, ename)
- else:
- a['project'] = (SET, '')
- # optimization: cache filtered results
- d = None
- for v in cache.keys():
- if cmp(a, eval(v)) == 0:
- d = cache[v]
- break
- if d is None:
- d = self.filter(JOB, a, bslist=jobs, attrop=PTL_AND,
- idonly=False, db_access=db_access)
- cache[str(a)] = d
- if not d or 'job_state=R' not in d:
- # in the absence of jobs, display limits defined with usage
- # of 0
- if ename is not None:
- _u = {ename: 0}
- else:
- _u = {_n: 0}
- else:
- if _t in ('u', 'o'):
- _u = calc_usage(
- d['job_state=R'], 'euser', _n, lim.resource)
- # an overall limit applies across all running jobs
- if _t == 'o':
- all_used = sum(_u.values())
- for k in _u.keys():
- _u[k] = all_used
- elif _t == 'g':
- _u = calc_usage(
- d['job_state=R'], 'egroup', _n, lim.resource)
- elif _t == 'p':
- _u = calc_usage(
- d['job_state=R'], 'project', _n, lim.resource)
- for k, used in _u.items():
- if not over or (int(used) > int(lim.value)):
- if ename is not None and k != ename:
- continue
- if _n in ('PBS_GENERIC', 'PBS_ALL'):
- if k not in ('PBS_GENERIC', 'PBS_ALL'):
- k += '/' + _n
- elif _n != k:
- continue
- tmp_linfo = create_linfo(lim, _t, k, used)
- linfo.append(tmp_linfo)
- del a
- del cache
- return linfo
- def __insert_jobs_in_db(self, jobs, hostname=None):
- """
- An experimental interface that converts jobs from file
- into entries in the PBS database that can be recovered
- upon server restart if all other ``objects``, ``queues``,
- ``resources``, etc... are already defined.
- The interface to PBS used in this method is incomplete
- and will most likely cause serious issues. Use only for
- development purposes
- """
- if not jobs:
- return []
- if hostname is None:
- hostname = socket.gethostname()
- # a very crude, and not quite maintainale way to get the flag value
- # of an attribute. This is one of the reasons why this conversion
- # of jobs is highly experimental
- flag_map = {'ctime': 9, 'qtime': 9, 'hop_count': 9, 'queue_rank': 9,
- 'queue_type': 9, 'etime': 9, 'job_kill_delay': 9,
- 'run_version': 9, 'job_state': 9, 'exec_host': 9,
- 'exec_host2': 9, 'exec_vnode': 9, 'mtime': 9, 'stime': 9,
- 'substate': 9, 'hashname': 9, 'comment': 9, 'run_count': 9,
- 'schedselect': 13}
- state_map = {'Q': 1, 'H': 2, 'W': 3, 'R': 4, 'E': 5, 'X': 6, 'B': 7}
- job_attr_stmt = ("INSERT INTO pbs.job_attr (ji_jobid, attr_name, "
- "attr_resource, attr_value, attr_flags)")
- job_stmt = ("INSERT INTO pbs.job (ji_jobid, ji_sv_name, ji_state, "
- "ji_substate,ji_svrflags, ji_numattr,"
- " ji_ordering, ji_priority, ji_stime, ji_endtbdry, "
- "ji_queue, ji_destin, ji_un_type, ji_momaddr, "
- "ji_momport, ji_exitstat, ji_quetime, ji_rteretry, "
- "ji_fromsock, ji_fromaddr, ji_4jid, ji_4ash, "
- "ji_credtype, ji_qrank, ji_savetm, ji_creattm)")
- all_stmts = []
- for job in jobs:
- keys = []
- values = []
- flags = []
- for k, v in job.items():
- if k in ('id', 'Mail_Points', 'Mail_Users'):
- continue
- keys.append(k)
- if not v.isdigit():
- values.append("'" + v + "'")
- else:
- values.append(v)
- if k in flag_map:
- flags.append(flag_map[k])
- elif k.startswith('Resource_List'):
- flags.append(15)
- else:
- flags.append(11)
- jobid = job['id'].split('.')[0] + '.' + hostname
- for i in range(len(keys)):
- stmt = job_attr_stmt
- stmt += " VALUES('" + jobid + "', "
- if '.' in keys[i]:
- k, v = keys[i].split('.')
- stmt += "'" + k + "', '" + v + "'" + ", "
- else:
- stmt += "'" + keys[i] + "', ''" + ", "
- stmt += values[i] + "," + str(flags[i])
- stmt += ");"
- self.logger.debug(stmt)
- all_stmts.append(stmt)
- js = job['job_state']
- svrflags = 1
- state = 1
- if js in state_map:
- state = state_map[js]
- if state == 4:
- # Other states svrflags aren't handled and will
- # cause issues, another reason this is highly experimental
- svrflags = 12289
- tm = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
- stmt = job_stmt
- stmt += " VALUES('" + jobid + "', 1, "
- stmt += str(state) + ", " + job['substate']
- stmt += ", " + str(svrflags)
- stmt += ", 0, 0, 0"
- if 'stime' in job:
- print job['stime']
- st = time.strptime(job['stime'], "%a %b %d %H:%M:%S %Y")
- stmt += ", " + str(time.mktime(st))
- else:
- stmt += ", 0"
- stmt += ", 0"
- stmt += ", '" + job['queue'] + "'"
- if 'exec_host2' in job:
- stmt += ", " + job['exec_host2']
- else:
- stmt += ", ''"
- stmt += ", 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0"
- stmt += ", '" + tm + "', '" + tm + "');"
- self.logger.debug(stmt)
- all_stmts.append(stmt)
- return all_stmts
- def clusterize(self, conf_file=None, hosts=None, import_jobs=False,
- db_creds_file=None):
- """
- Mimic a ``pbs_diag`` snapshot onto a set of hosts running
- a PBS ``server``,``scheduler``, and ``MoM``.
- This method clones the following information from the diag:
- ``Server attributes``
- ``Server resourcedef``
- ``Hooks``
- ``Scheduler configuration``
- ``Scheduler resource_group``
- ``Scheduler holiday file``
- ``Per Queue attributes``
- Nodes are copied as a vnode definition file inserted into
- each host's MoM instance.
- Currently no support for cloning the server 'sched' object,
- nor to copy nodes to multi-mom instances.
- Jobs are copied over only if import_jobs is True, see below
- for details
- :param asdiag: Path to the pbs_diag snapshot to use
- :type asdiag: str
- :param conf_file: Configuration file for the MoM instance
- :param hosts: List of hosts on which to clone the diag
- snapshot
- :type hosts: List
- :param include_jobs: [Experimental] if True jobs from the
- pbs_diag are imported into the host's
- database. There are several caveats to
- this option:
- The scripts are not imported
- The users and groups are not created on
- the local system.There are no actual
- processes created on the MoM for each
- job so operations on the job such as
- signals or delete will fail (delete -W
- force will still work)
- :type include_jobs: bool
- :param db_creds_file: Path to file containing credentials
- to access the DB
- :type db_creds_file: str or None
- """
- if not self.has_diag:
- return
- if hosts is None:
- return
- for h in hosts:
- svr = Server(h)
- sched = Scheduler(server=svr, diag=self.diag, diagmap=self.diagmap)
- try:
- svr.manager(MGR_CMD_DELETE, NODE, None, id="")
- except:
- pass
- svr.revert_to_defaults(delqueues=True, delhooks=True)
- local = svr.pbs_conf['PBS_HOME']
- diag_rdef = os.path.join(self.diag, 'server_priv', 'resourcedef')
- diag_sc = os.path.join(self.diag, 'sched_priv', 'sched_config')
- diag_rg = os.path.join(self.diag, 'sched_priv', 'resource_group')
- diag_hldy = os.path.join(self.diag, 'sched_priv', 'holidays')
- nodes = os.path.join(self.diag, 'pbsnodes_va.out')
- diag_hooks = os.path.join(self.diag, 'qmgr_ph.out')
- diag_ps = os.path.join(self.diag, 'qmgr_ps.out')
- local_rdef = os.path.join(local, 'server_priv', 'resourcedef')
- local_sc = os.path.join(local, 'sched_priv', 'sched_config')
- local_rg = os.path.join(local, 'sched_priv', 'resource_group')
- local_hldy = os.path.join(local, 'sched_priv', 'holidays')
- _fcopy = [(diag_rdef, local_rdef), (diag_sc, local_sc),
- (diag_rg, local_rg), (diag_hldy, local_hldy)]
- # Restart since resourcedef may have changed
- svr.restart()
- if os.path.isfile(diag_ps):
- tmp_ps = open(diag_ps)
- cmd = [os.path.join(svr.pbs_conf['PBS_EXEC'], 'bin', 'qmgr')]
- self.du.run_cmd(h, cmd, stdin=tmp_ps, sudo=True, logerr=False)
- tmp_ps.close()
- # Unset any site-sensitive attributes
- for a in ['pbs_license_info', 'manager', 'operators',
- 'mail_from', 'acl_roots', 'acl_hosts']:
- try:
- svr.manager(MGR_CMD_UNSET, SERVER, a, sudo=True)
- except:
- pass
- for (d, l) in _fcopy:
- if os.path.isfile(d):
- self.logger.info('copying ' + d + ' to ' + l)
- self.du.run_copy(h, src=d, dest=l, sudo=True)
- diag_sched = self.status(SCHED)
- for ds in diag_sched:
- for k, v in ds.items():
- if k != 'id':
- try:
- svr.manager(MGR_CMD_SET, SCHED, {k: v},
- logerr=False)
- except PbsManagerError:
- self.logger.warning(
- 'Skipping sched attribute ' + k)
- sched.signal('-HUP')
- if os.path.isfile(nodes):
- f = open(nodes)
- lines = f.readlines()
- f.close()
- dl = self.utils.convert_to_dictlist(lines)
- vdef = self.utils.dictlist_to_vnodedef(dl)
- if vdef:
- try:
- svr.manager(MGR_CMD_DELETE, NODE, None, "")
- except:
- pass
- MoM(h, pbsconf_file=conf_file).insert_vnode_def(vdef)
- svr.restart()
- svr.manager(MGR_CMD_CREATE, NODE, id=svr.shortname)
- # check if any node is associated to a queue.
- # This is needed because the queues 'hasnodes' attribute
- # does not get set through vnode def update and must be set
- # via qmgr. It only needs to be set once, not for each node
- qtoset = {}
- for n in dl:
- if 'queue' in n and n['queue'] not in qtoset:
- qtoset[n['queue']] = n['id']
- # before setting queue on nodes make sure that the vnode
- # def is all set
- svr.expect(NODE, {'state=free': (GE, len(dl))}, interval=3)
- for k, v in qtoset.items():
- svr.manager(MGR_CMD_SET, NODE, {'queue': k}, id=v)
- # populate hooks
- if os.path.isfile(diag_hooks):
- tmp_hook = open(diag_hooks)
- cmd = [os.path.join(svr.pbs_conf['PBS_EXEC'], 'bin', 'qmgr')]
- self.du.run_cmd(h, cmd, stdin=tmp_hook, sudo=True)
- tmp_hook.close()
- # import jobs
- if import_jobs is not None:
- jobs = self.status(JOB)
- sql_stmt = self.__insert_jobs_in_db(jobs, h)
- print "\n".join(sql_stmt)
- if db_creds_file is not None:
- pass
- class EquivClass(PBSObject):
- """
- Equivalence class holds information on a collection of entities
- grouped according to a set of attributes
- :param attributes: Dictionary of attributes
- :type attributes: Dictionary
- :param entities: List of entities
- :type entities: List
- """
- def __init__(self, name, attributes={}, entities=[]):
- self.name = name
- self.attributes = attributes
- self.entities = entities
- self.logger = logging.getLogger(__name__)
- def add_entity(self, entity):
- """
- Add entities
- :param entity: Entity to add
- :type entity: str
- """
- if entity not in self.entities:
- self.entities.append(entity)
- def __str__(self):
- s = [str(len(self.entities)), ":", ":".join(self.name)]
- return "".join(s)
- def show(self, showobj=False):
- """
- Show the entities
- :param showobj: If true then show the entities
- :type showobj: bool
- """
- s = " && ".join(self.name) + ': '
- if showobj:
- s += str(self.entities)
- else:
- s += str(len(self.entities))
- print s
- return s
- class Resource(PBSObject):
- """
- PBS resource referenced by name, type and flag
- :param name: Resource name
- :type name: str or None
- :param type: Type of resource
- """
- def __init__(self, name=None, type=None, flag=None):
- PBSObject.__init__(self, name)
- self.set_name(name)
- self.set_type(type)
- self.set_flag(flag)
- def set_name(self, name):
- """
- Set the resource name
- """
- self.name = name
- self.attributes['id'] = name
- def set_type(self, type):
- """
- Set the resource type
- """
- self.type = type
- self.attributes['type'] = type
- def set_flag(self, flag):
- """
- Set the flag
- """
- self.flag = flag
- self.attributes['flag'] = flag
- def __str__(self):
- s = [self.attributes['id']]
- if 'type' in self.attributes:
- s.append('type=' + self.attributes['type'])
- if 'flag' in self.attributes:
- s.append('flag=' + self.attributes['flag'])
- return " ".join(s)
- class Holidays():
- """
- Descriptive calss for Holiday file.
- """
- def __init__(self):
- self.year = {'id': "YEAR", 'value': None, 'valid': False}
- self.weekday = {'id': "weekday", 'p': None, 'np': None, 'valid': None,
- 'position': None}
- self.monday = {'id': "monday", 'p': None, 'np': None, 'valid': None,
- 'position': None}
- self.tuesday = {'id': "tuesday", 'p': None, 'np': None, 'valid': None,
- 'position': None}
- self.wednesday = {'id': "wednesday", 'p': None, 'np': None,
- 'valid': None, 'position': None}
- self.thursday = {'id': "thursday", 'p': None, 'np': None,
- 'valid': None, 'position': None}
- self.friday = {'id': "friday", 'p': None, 'np': None, 'valid': None,
- 'position': None}
- self.saturday = {'id': "saturday", 'p': None, 'np': None,
- 'valid': None, 'position': None}
- self.sunday = {'id': "sunday", 'p': None, 'np': None, 'valid': None,
- 'position': None}
- self.days_set = [] # list of set days
- self._days_map = {'weekday': self.weekday, 'monday': self.monday,
- 'tuesday': self.tuesday, 'wednesday': self.wednesday,
- 'thursday': self.thursday, 'friday': self.friday,
- 'saturday': self.saturday, 'sunday': self.sunday}
- self.holidays = [] # list of calendar holidays
- def __str__(self):
- """
- Return the content to write to holidays file as a string
- """
- content = []
- if self.year['valid']:
- content.append(self.year['id'] + "\t" +
- self.year['value'])
- for i in range(0, len(self.days_set)):
- content.append(self.days_set[i]['id'] + "\t" +
- self.days_set[i]['p'] + "\t" +
- self.days_set[i]['np'])
- # Add calendar holidays
- for day in self.holidays:
- content.append(day)
- return "\n".join(content)
- class Scheduler(PBSService):
- """
- Container of Scheduler related properties
- :param hostname: The hostname on which the scheduler instance
- is operating
- :type hostname: str or None
- :param server: A PBS server instance to which this scheduler
- is associated
- :param pbsconf_file: path to a PBS configuration file
- :type pbsconf_file: str or None
- :param diagmap: A dictionary of PBS objects (node,server,etc)
- to mapped files from PBS diag directory
- :type diagmap: Dictionary
- :param diag: path to PBS diag directory (This will overrides
- diagmap)
- :type diag: str or None
- :param db_acccess: set to either file containing credentials
- to DB access or dictionary containing
- ``{'dbname':...,'user':...,'port':...}``
- :type db_access: str or dictionary
- """
- # A vanilla scheduler configuration. This set may change based on
- # updates to PBS
- sched_dflt_config = {
- "backfill": "true ALL",
- "backfill_prime": "false ALL",
- "help_starving_jobs": "true ALL",
- "max_starve": "24:00:00",
- "strict_ordering": "false ALL",
- "provision_policy": "\"aggressive_provision\"",
- "preempt_order": "\"SCR\"",
- "fairshare_entity": "euser",
- "dedicated_prefix": "ded",
- "primetime_prefix": "p_",
- "nonprimetime_prefix": "np_",
- "preempt_queue_prio": "150",
- "preempt_prio": "\"express_queue, normal_jobs\"",
- "load_balancing": "false ALL",
- "prime_exempt_anytime_queues": "false",
- "round_robin": "False all",
- "fairshare_usage_res": "cput",
- "smp_cluster_dist": "pack",
- "fair_share": "false ALL",
- "preempt_sort": "min_time_since_start",
- "node_sort_key": "\"sort_priority HIGH\" ALL",
- "sort_queues": "true ALL",
- "by_queue": "True ALL",
- "preemptive_sched": "true ALL",
- "resources": "\"ncpus, mem, arch, host, vnode, aoe\"",
- "log_filter": "3328 ",
- }
- sched_config_options = ["node_group_key",
- "dont_preempt_starving",
- "fairshare_enforce_no_shares",
- "strict_ordering",
- "resource_unset_infinite",
- "sync_time",
- "unknown_shares",
- "log_filter",
- "dedicated_prefix",
- "load_balancing",
- "help_starving_jobs",
- "max_starve",
- "sort_queues",
- "backfill",
- "primetime_prefix",
- "nonprimetime_prefix",
- "backfill_prime",
- "prime_exempt_anytime_queues",
- "prime_spill",
- "prime_exempt_anytime_queues",
- "prime_spill",
- "resources",
- "mom_resources",
- "smp_cluster_dist",
- "preempt_queue_prio",
- "preempt_suspend",
- "preempt_checkpoint",
- "preempt_requeue",
- "preemptive_sched",
- "dont_preempt_starving",
- "node_group_key",
- "dont_preempt_starving",
- "fairshare_enforce_no_shares",
- "strict_ordering",
- "resource_unset_infinite",
- "provision_policy",
- "resv_confirm_ignore",
- "allow_aoe_calendar",
- "max_job_check",
- "preempt_attempts",
- "update_comments",
- "sort_by",
- "key",
- "preempt_starving",
- "preempt_fairshare",
- "load_balancing_rr",
- "assign_ssinodes",
- "cpus_per_ssinode",
- "mem_per_ssinode",
- "strict_fifo",
- "mem_per_ssinode",
- "strict_fifo"
- ]
- fs_re = '(?P<name>[\S]+)[\s]*:[\s]*Grp:[\s]*(?P<Grp>[-]*[0-9]*)' + \
- '[\s]*cgrp:[\s]*(?P<cgrp>[-]*[0-9]*)[\s]*' + \
- 'Shares:[\s]*(?P<Shares>[-]*[0-9]*)[\s]*Usage:[\s]*' + \
- '(?P<Usage>[0-9]+)[\s]*Perc:[\s]*(?P<Perc>.*)%'
- fs_tag = re.compile(fs_re)
- def __init__(self, hostname=None, server=None, pbsconf_file=None,
- diagmap={}, diag=None, db_access=None, id='default',
- sched_priv=None):
- self.sched_config_file = None
- self.dflt_holidays_file = None
- self.holidays_file = None
- self.sched_config = {}
- self._sched_config_comments = {}
- self._config_order = []
- self.dedicated_time_file = None
- self.dedicated_time = None
- self.dedicated_time_as_str = None
- self.fairshare_tree = None
- self.resource_group = None
- self.holidays_obj = None
- self.server = None
- self.server_dyn_res = None
- self.logger = logging.getLogger(__name__)
- self.db_access = None
- if server is not None:
- self.server = server
- if diag is None and self.server.diag is not None:
- diag = self.server.diag
- if (len(diagmap) == 0) and (len(self.server.diagmap) != 0):
- diagmap = self.server.diagmap
- else:
- self.server = Server(hostname, pbsconf_file=pbsconf_file,
- db_access=db_access, diag=diag,
- diagmap=diagmap)
- if hostname is None:
- hostname = self.server.hostname
- PBSService.__init__(self, hostname, pbsconf_file=pbsconf_file,
- diag=diag, diagmap=diagmap)
- _m = ['scheduler ', self.shortname]
- if pbsconf_file is not None:
- _m += ['@', pbsconf_file]
- _m += [': ']
- self.logprefix = "".join(_m)
- self.pi = PBSInitServices(hostname=self.hostname,
- conf=self.pbs_conf_file)
- self.pbs_conf = self.server.pbs_conf
- self.sc_name = id
- self.dflt_sched_config_file = os.path.join(self.pbs_conf['PBS_EXEC'],
- 'etc', 'pbs_sched_config')
- self.dflt_holidays_file = os.path.join(self.pbs_conf['PBS_EXEC'],
- 'etc', 'pbs_holidays')
- self.dflt_resource_group_file = os.path.join(self.pbs_conf['PBS_EXEC'],
- 'etc',
- 'pbs_resource_group')
- self.setup_sched_priv(sched_priv)
- self.db_access = db_access
- self.version = None
- def setup_sched_priv(self, sched_priv=None):
- """
- Initialize Scheduler() member variables on initialization or if
- sched_priv changes
- """
- if sched_priv is None:
- if 'sched_priv' in self.attributes:
- sched_priv = self.attributes['sched_priv']
- else:
- sched_priv = os.path.join(self.pbs_conf['PBS_HOME'],
- 'sched_priv')
- self.sched_config_file = os.path.join(sched_priv, 'sched_config')
- self.resource_group_file = os.path.join(sched_priv, 'resource_group')
- self.holidays_file = os.path.join(sched_priv, 'holidays')
- if not os.path.exists(sched_priv):
- return
- self.parse_sched_config()
- self.fairshare_tree = self.query_fairshare()
- rg = self.parse_resource_group(self.hostname, self.resource_group_file)
- self.resource_group = rg
- self.holidays_obj = Holidays()
- self.holidays_parse_file(level=logging.DEBUG)
- def initialise_service(self):
- """
- initialise the scheduler object
- """
- PBSService.initialise_service(self)
- try:
- attrs = self.server.status(SCHED, level=logging.DEBUG,
- db_access=self.db_access,
- id=self.sc_name)
- if attrs is not None and len(attrs) > 0:
- self.attributes = attrs[0]
- except (PbsManagerError, PbsStatusError), e:
- self.logger.error('Error querying scheduler %s' % e.msg)
- def isUp(self):
- """
- Check for PBS scheduler up
- """
- return super(Scheduler, self)._isUp(self)
- def signal(self, sig):
- """
- Send a signal to PBS scheduler
- """
- self.logger.info('scheduler ' + self.shortname + ': sent signal ' +
- sig)
- return super(Scheduler, self)._signal(sig, inst=self)
- def get_pid(self):
- """
- Get the PBS scheduler pid
- """
- return super(Scheduler, self)._get_pid(inst=self)
- def all_instance_pids(self):
- """
- Get the all pids for the instance
- """
- return super(Scheduler, self)._all_instance_pids(inst=self)
- def start(self, sched_home=None, args=None, launcher=None):
- """
- Start the scheduler
- :param sched_home: Path to scheduler log and home directory
- :type sched_home: str
- :param args: Arguments required to start the scheduler
- :type args: str
- :param launcher: Optional utility to invoke the launch of the service
- :type launcher: str or list
- """
- if self.attributes['id'] != 'default':
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'],
- 'sbin', 'pbs_sched')]
- cmd += ['-I', self.attributes['id']]
- cmd += ['-S', str(self.attributes['sched_port'])]
- if sched_home is not None:
- cmd += ['-d', sched_home]
- try:
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True,
- logerr=False, level=logging.INFOCLI)
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg)
- self.server.manager(MGR_CMD_LIST, SCHED)
- return ret
- if args is not None or launcher is not None:
- return super(Scheduler, self)._start(inst=self, args=args,
- launcher=launcher)
- else:
- try:
- rv = self.pi.start_sched()
- self._update_pid(self)
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg)
- return rv
- def stop(self, sig=None):
- """
- Stop the PBS scheduler
- :param sig: Signal to stop the PBS scheduler
- :type sig: str
- """
- if sig is not None:
- self.logger.info(self.logprefix + 'stopping Scheduler on host ' +
- self.hostname)
- return super(Scheduler, self)._stop(sig, inst=self)
- else:
- try:
- self.pi.stop_sched()
- self.pid = None
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg)
- return True
- def restart(self):
- """
- Restart the PBS scheduler
- """
- if self.isUp():
- if not self.stop():
- return False
- return self.start()
- def log_match(self, msg=None, id=None, n=50, tail=True, allmatch=False,
- regexp=False, max_attempts=None, interval=None,
- starttime=None, endtime=None, level=logging.INFO,
- existence=True):
- """
- Match given ``msg`` in given ``n`` lines of Scheduler log
- :param msg: log message to match, can be regex also when
- ``regexp`` is True
- :type msg: str
- :param id: The id of the object to trace. Only used for
- tracejob
- :type id: str
- :param n: 'ALL' or the number of lines to search through,
- defaults to 50
- :type n: str or int
- :param tail: If true (default), starts from the end of
- the file
- :type tail: bool
- :param allmatch: If True all matching lines out of then
- parsed are returned as a list. Defaults
- to False
- :type allmatch: bool
- :param regexp: If true msg is a Python regular expression.
- Defaults to False
- :type regexp: bool
- :param max_attempts: the number of attempts to make to find
- a matching entry
- :type max_attempts: int
- :param interval: the interval between attempts
- :type interval: int
- :param starttime: If set ignore matches that occur before
- specified time
- :type starttime: int
- :param endtime: If set ignore matches that occur after
- specified time
- :type endtime: int
- :param level: The logging level, defaults to INFO
- :type level: int
- :param existence: If True (default), check for existence of
- given msg, else check for non-existence of
- given msg.
- :type existence: bool
- :return: (x,y) where x is the matching line
- number and y the line itself. If allmatch is True,
- a list of tuples is returned.
- :rtype: tuple
- :raises PtlLogMatchError:
- When ``existence`` is True and given
- ``msg`` is not found in ``n`` line
- Or
- When ``existence`` is False and given
- ``msg`` found in ``n`` line.
- .. note:: The matching line number is relative to the record
- number, not the absolute line number in the file.
- """
- return self._log_match(self, msg, id, n, tail, allmatch, regexp,
- max_attempts, interval, starttime, endtime,
- level=level, existence=existence)
- def pbs_version(self):
- """
- Get the version of the scheduler instance
- """
- if self.version:
- return self.version
- version = self.log_match('pbs_version', tail=False)
- if version:
- version = version[1].strip().split('=')[1]
- else:
- version = "unknown"
- self.version = LooseVersion(version)
- return self.version
- def parse_sched_config(self, schd_cnfg=None):
- """
- Parse a sceduling configuration file into a dictionary.
- Special handling of identical keys ``(e.g., node_sort_key)``
- is done by appending a delimiter, '%', between each value
- of the key. When printed back to file, each delimited entry
- gets written on a line of its own. For example, the python
- dictionary entry:
- ``{'node_sort_key':
- ["ncpus HIGH unusued" prime", "node_priority HIH"
- non-prime"]}``
- will get written as:
- ``node_sort_key: "ncpus HIGH unusued" prime``
- ``node_sort_key: "node_priority HIGH" non-prime``
- Returns sched_config dictionary that gets reinitialized
- every time this method is called.
- """
- # sched_config is initialized
- if self.sched_config:
- del(self.sched_config)
- self.sched_config = {}
- self._sched_config_comments = {}
- self._config_order = []
- if schd_cnfg is None:
- if self.sched_config_file is not None:
- schd_cnfg = self.sched_config_file
- else:
- self.logger.error('no scheduler configuration file to parse')
- return False
- try:
- conf_opts = self.du.cat(self.hostname, schd_cnfg,
- sudo=(not self.has_diag),
- level=logging.DEBUG2)['out']
- except:
- self.logger.error('error parsing scheduler configuration')
- return False
- _comment = []
- conf_re = re.compile(
- '[#]?[\s]*(?P<conf_id>[\w]+):[\s]*(?P<conf_val>.*)')
- for line in conf_opts:
- m = conf_re.match(line)
- if m:
- key = m.group('conf_id')
- val = m.group('conf_val')
- # line is a comment, it could be a commented out scheduling
- # option, or the description of an option. It could also be
- # that part of the description is an example setting of the
- # option.
- # We must keep track of commented out options in order to
- # rewrite the configuration in the same order as it was defined
- if line.startswith('#'):
- if key in self.sched_config_options:
- _comment += [line]
- if key in self._sched_config_comments:
- self._sched_config_comments[key] += _comment
- _comment = []
- else:
- self._sched_config_comments[key] = _comment
- _comment = []
- if key not in self._config_order:
- self._config_order.append(key)
- else:
- _comment += [line]
- continue
- if key not in self._sched_config_comments:
- self._sched_config_comments[key] = _comment
- else:
- self._sched_config_comments[key] += _comment
- if key not in self._config_order:
- self._config_order.append(key)
- _comment = []
- if key in self.sched_config:
- if isinstance(self.sched_config[key], list):
- if isinstance(val, list):
- self.sched_config[key].extend(val)
- else:
- self.sched_config[key].append(val)
- else:
- if isinstance(val, list):
- self.sched_config[key] = [self.sched_config[key]]
- self.sched_config[key].extend(val)
- else:
- self.sched_config[key] = [self.sched_config[key],
- val]
- else:
- self.sched_config[key] = val
- else:
- _comment += [line]
- self._sched_config_comments['PTL_SCHED_CONFIG_TAIL'] = _comment
- return True
- def check_defaults(self, config):
- """
- Check the values in argument config against default values
- """
- if len(config.keys()) == 0:
- return
- for k, v in self.sched_dflt_config.items():
- if k in config:
- s1 = v
- s1 = s1.replace(" ", "")
- s1 = s1.replace("\t", "").strip()
- s2 = config[k]
- s2 = s2.replace(" ", "")
- s2 = s2.replace("\t", "").strip()
- if s1 != s2:
- self.logger.debug(k + ' non-default: ' + v +
- ' != ' + config[k])
- def apply_config(self, config=None, validate=True, path=None):
- """
- Apply the configuration specified by config
- :param config: Configurations to set. Default: self.
- sched_config
- :param validate: If True (the default) validate that
- settings did not yield an error.
- Validation is done by parsing the
- scheduler log which, in some cases may
- be slow and therefore undesirable.
- :type validate: bool
- :param path: Optional path to file to which configuration
- is written. If None, the configuration is
- written to PBS_HOME/sched_priv/sched_config
- :type path: str
- :returns: True on success and False otherwise. Success
- means that upon applying the new configuration
- the scheduler did not emit an
- "Error reading line" in its log file.
- """
- if config is None:
- config = self.sched_config
- if len(config) == 0:
- return True
- reconfig_time = int(time.time())
- try:
- fn = self.du.create_temp_file()
- with open(fn, "w") as fd:
- for k in self._config_order:
- if k in config:
- if k in self._sched_config_comments:
- fd.write("\n".join(self._sched_config_comments[k]))
- fd.write("\n")
- v = config[k]
- if isinstance(v, list):
- for val in v:
- fd.write(k + ": " + str(val) + "\n")
- else:
- fd.write(k + ": " + str(v) + "\n")
- elif k in self._sched_config_comments:
- fd.write("\n".join(self._sched_config_comments[k]))
- fd.write("\n")
- for k, v in self.sched_config.items():
- if k not in self._config_order:
- fd.write(k + ": " + str(v).strip() + "\n")
- if 'PTL_SCHED_CONFIG_TAIL' in self._sched_config_comments:
- fd.write("\n".join(
- self._sched_config_comments['PTL_SCHED_CONFIG_TAIL']))
- fd.write("\n")
- if path is None:
- if 'sched_priv' in self.attributes:
- sched_priv = self.attributes['sched_priv']
- else:
- sched_priv = os.path.join(self.pbs_conf['PBS_HOME'],
- "sched_priv")
- sp = os.path.join(sched_priv, "sched_config")
- else:
- sp = path
- self.du.run_copy(self.hostname, fn, sp, preserve_permission=False,
- sudo=True)
- os.remove(fn)
- self.logger.debug(self.logprefix + "updated configuration")
- except:
- m = self.logprefix + 'error in apply_config '
- self.logger.error(m + str(traceback.print_exc()))
- raise PbsSchedConfigError(rc=1, rv=False, msg=m)
- if validate:
- self.get_pid()
- self.signal('-HUP')
- try:
- self.log_match("Sched;reconfigure;Scheduler is reconfiguring",
- n=10, starttime=reconfig_time)
- self.log_match("Error reading line", n=10, max_attempts=2,
- starttime=reconfig_time, existence=False)
- except PtlLogMatchError:
- _msg = 'Error in validating sched_config changes'
- raise PbsSchedConfigError(rc=1, rv=False,
- msg=_msg)
- return True
- def set_sched_config(self, confs={}, apply=True, validate=True):
- """
- set a ``sched_config`` property
- :param confs: dictionary of key value sched_config entries
- :type confs: Dictionary
- :param apply: if True (the default), apply configuration.
- :type apply: bool
- :param validate: if True (the default), validate the
- configuration settings.
- :type validate: bool
- """
- self.parse_sched_config()
- self.logger.info(self.logprefix + "config " + str(confs))
- self.sched_config = dict(self.sched_config.items() + confs.items())
- if apply:
- try:
- self.apply_config(validate=validate)
- except PbsSchedConfigError:
- for k in confs:
- del self.sched_config[k]
- self.apply_config(validate=validate)
- return True
- def add_server_dyn_res(self, custom_resource, script_body=None,
- res_file=None, apply=True, validate=True):
- """
- Add a server dynamic resource script or file to the scheduler
- configuration
- :param custom_resource: The name of the custom resource to
- define
- :type custom_resource: str
- :param script_body: The body of the server dynamic resource
- :param res_file: Alternatively to passing the script body, use
- the file instead
- :type res_file: str or None
- :param apply: if True (the default), apply configuration.
- :type apply: bool
- :param validate: if True (the default), validate the
- configuration settings.
- :type validate: bool
- """
- if res_file is not None:
- f = open(file)
- script_body = f.readlines()
- f.close()
- else:
- res_file = self.du.create_temp_file(prefix='PtlPbsSchedConfig',
- body=script_body)
- self.server_dyn_res = res_file
- self.logger.info(self.logprefix + "adding server dyn res " + res_file)
- self.logger.info("-" * 30)
- self.logger.info(script_body)
- self.logger.info("-" * 30)
- self.du.chmod(self.hostname, path=res_file, mode=0755)
- a = {'server_dyn_res': '"' + custom_resource + ' !' + res_file + '"'}
- self.set_sched_config(a, apply=apply, validate=validate)
- def unset_sched_config(self, name, apply=True):
- """
- Delete a ``sched_config`` entry
- :param name: the entry to delete from sched_config
- :type name: str
- :param apply: if True, apply configuration. Defaults to True
- :type apply: bool
- """
- self.parse_sched_config()
- if name not in self.sched_config:
- return True
- self.logger.info(self.logprefix + "unsetting config " + name)
- del self.sched_config[name]
- if apply:
- return self.apply_config()
- def set_dedicated_time_file(self, file):
- """
- Set the path to a dedicated time
- """
- self.logger.info(self.logprefix + " setting dedicated time file to " +
- str(file))
- self.dedicated_time_file = file
- def revert_to_defaults(self):
- """
- Revert scheduler configuration to defaults.
- :returns: True on success, False otherwise
- """
- self.logger.info(self.logprefix +
- "reverting configuration to defaults")
- ignore_attrs = ['id', 'pbs_version', 'sched_host',
- 'state', 'sched_port']
- unsetattrs = []
- for k in self.attributes.keys():
- if k not in ignore_attrs:
- unsetattrs.append(k)
- if len(unsetattrs) > 0:
- self.server.manager(MGR_CMD_UNSET, SCHED, unsetattrs)
- self.clear_dedicated_time(hup=False)
- if self.du.cmp(self.hostname, self.dflt_resource_group_file,
- self.resource_group_file, sudo=True) != 0:
- self.du.run_copy(self.hostname, self.dflt_resource_group_file,
- self.resource_group_file,
- preserve_permission=False,
- sudo=True)
- if self.server_dyn_res is not None:
- self.du.rm(self.hostname, self.server_dyn_res, force=True,
- sudo=True)
- self.server_dyn_res = None
- rc = self.holidays_revert_to_default()
- if self.du.cmp(self.hostname, self.dflt_sched_config_file,
- self.sched_config_file, sudo=True) != 0:
- self.du.run_copy(self.hostname, self.dflt_sched_config_file,
- self.sched_config_file, preserve_permission=False,
- sudo=True)
- self.signal('-HUP')
- # Revert fairshare usage
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbsfs'), '-e']
- if self.sc_name is not 'default':
- cmd += ['-I', self.sc_name]
- self.du.run_cmd(cmd=cmd, sudo=True)
- self.parse_sched_config()
- if self.platform == 'cray' or self.platform == 'craysim':
- self.add_resource('vntype')
- self.add_resource('hbmem')
- self.fairshare_tree = None
- self.resource_group = None
- return self.isUp()
- def create_scheduler(self, sched_home=None):
- """
- Start scheduler with creating required directories for scheduler
- :param sched_home: path of scheduler home and log directory
- :type sched_home: str
- """
- if sched_home is None:
- sched_home = self.server.pbs_conf['PBS_HOME']
- sched_priv_dir = os.path.join(sched_home,
- self.attributes['sched_priv'])
- sched_logs_dir = os.path.join(sched_home,
- self.attributes['sched_log'])
- if not os.path.exists(sched_priv_dir):
- self.du.mkdir(path=sched_priv_dir, sudo=True)
- self.du.run_copy(self.hostname, self.dflt_resource_group_file,
- self.resource_group_file, mode=0644,
- sudo=True)
- self.du.run_copy(self.hostname, self.dflt_holidays_file,
- self.holidays_file, mode=0644, sudo=True)
- self.du.run_copy(self.hostname, self.dflt_sched_config_file,
- self.sched_config_file, mode=0644,
- sudo=True)
- if not os.path.exists(sched_logs_dir):
- self.du.mkdir(path=sched_logs_dir, sudo=True)
- self.setup_sched_priv(sched_priv=sched_priv_dir)
- def save_configuration(self, outfile, mode='a'):
- """
- Save scheduler configuration
- :param outfile: Path to a file to which configuration
- is saved
- :type outfile: str
- :param mode: mode to use to access outfile. Defaults to
- append, 'a'.
- :type mode: str
- :returns: True on success and False otherwise
- """
- conf = {}
- sconf = {MGR_OBJ_SCHED: conf}
- if 'sched_priv' in self.attributes:
- sched_priv = self.attributes['sched_priv']
- else:
- sched_priv = os.path.join(
- self.pbs_conf['PBS_HOME'], 'sched_priv')
- sc = os.path.join(sched_priv, 'sched_config')
- self._save_config_file(conf, sc)
- rg = os.path.join(sched_priv, 'resource_group')
- self._save_config_file(conf, rg)
- dt = os.path.join(sched_priv, 'dedicated_time')
- self._save_config_file(conf, dt)
- hd = os.path.join(sched_priv, 'holidays')
- self._save_config_file(conf, hd)
- try:
- f = open(outfile, mode)
- cPickle.dump(sconf, f)
- f.close()
- except:
- self.logger.error('error saving configuration ' + outfile)
- return False
- return True
- def load_configuration(self, infile):
- """
- load configuration from saved file infile
- """
- self._load_configuration(infile, MGR_OBJ_SCHED)
- def get_resources(self, exclude=[]):
- """
- returns a list of allocatable resources.
- :param exclude: if set, excludes the named resources, if
- they exist, from the resulting list
- :type exclude: List
- """
- if 'resources' not in self.sched_config:
- return None
- resources = self.sched_config['resources']
- resources = resources.replace('"', '')
- resources = resources.replace(' ', '')
- res = resources.split(',')
- if len(exclude) > 0:
- for e in exclude:
- if e in res:
- res.remove(e)
- return res
- def add_resource(self, name, apply=True):
- """
- Add a resource to ``sched_config``.
- :param name: the resource name to add
- :type name: str
- :param apply: if True, apply configuration. Defaults to True
- :type apply: bool
- :returns: True on success and False otherwise.
- Return True if the resource is already defined.
- """
- # if the sched_config has not been read in yet, parse it
- if not self.sched_config:
- self.parse_sched_config()
- if 'resources' in self.sched_config:
- resources = self.sched_config['resources']
- resources = resources.replace('"', '')
- splitres = [r.strip() for r in resources.split(",")]
- if name in splitres:
- return True
- resources = '"' + resources + ', ' + name + '"'
- else:
- resources = '"' + name + '"'
- return self.set_sched_config({'resources': resources}, apply=apply)
- def remove_resource(self, name, apply=True):
- """
- Remove a resource to ``sched_config``.
- :param name: the resource name to remove
- :type name: str
- :param apply: if True, apply configuration. Defaults to True
- :type apply: bool
- :returns: True on success and False otherwise
- """
- # if the sched_config has not been read in yet, parse it
- if not self.sched_config:
- self.parse_sched_config()
- if 'resources' in self.sched_config:
- resources = self.sched_config['resources']
- resources = resources.replace('"', '')
- splitres = [r.strip() for r in resources.split(",")]
- if name not in splitres:
- return True
- newres = []
- for r in splitres:
- if r != name:
- newres.append(r)
- resources = '"' + ",".join(newres) + '"'
- return self.set_sched_config({'resources': resources}, apply=apply)
- def holidays_revert_to_default(self, level=logging.INFO):
- """
- Revert holidays file to default
- """
- self.logger.log(level, self.logprefix +
- "reverting holidays file to default")
- rc = None
- # Copy over the holidays file from PBS_EXEC if it exists
- if self.du.cmp(self.hostname, self.dflt_holidays_file,
- self.holidays_file, sudo=True) != 0:
- ret = self.du.run_copy(self.hostname, self.dflt_holidays_file,
- self.holidays_file,
- preserve_permission=False, sudo=True,
- logerr=True)
- rc = ret['rc']
- # Update the internal data structures for the updated file
- self.holidays_parse_file(level=level)
- else:
- rc = 1
- return rc
- def holidays_parse_file(self, path=None, obj=None, level=logging.INFO):
- """
- Parse the existing holidays file
- :param path: optional path to the holidays file to parse
- :type path: str or None
- :param obj: optional holidays object to be used instead
- of internal
- :returns: The content of holidays file as a list of lines
- """
- self.logger.log(level, self.logprefix + "Parsing holidays file")
- if obj is None:
- obj = self.holidays_obj
- days_map = obj._days_map
- days_set = obj.days_set
- if path is None:
- path = self.holidays_file
- lines = self.du.cat(self.hostname, path, sudo=True)['out']
- content = [] # valid content to return
- self.holidays_delete_entry(
- 'a', apply=False, obj=obj, level=logging.DEBUG)
- for line in lines:
- entry = str(line).split()
- if len(entry) == 0:
- continue
- tag = entry[0].lower()
- if tag == "year": # initialize year
- content.append("\t".join(entry))
- obj.year['valid'] = True
- if len(entry) > 1:
- obj.year['value'] = entry[1]
- elif tag in days_map.keys(): # initialize a day
- content.append("\t".join(entry))
- day = days_map[tag]
- day['valid'] = True
- days_set.append(day)
- day['position'] = len(days_set) - 1
- if len(entry) > 1:
- day['p'] = entry[1]
- if len(entry) > 2:
- day['np'] = entry[2]
- elif tag.isdigit(): # initialize a holiday
- content.append("\t".join(entry))
- obj.holidays.append(tag)
- else:
- pass
- return content
- def holidays_set_day(self, day_id, prime="", nonprime="", apply=True,
- obj=None, level=logging.INFO):
- """
- Set prime time values for a day
- :param day_id: the day to be set (string)
- :type day_id: str
- :param prime: the prime time value
- :param nonprime: the non-prime time value
- :param apply: to reflect the changes to file
- :type apply: bool
- :param obj: optional holidays object to be used instead
- of internal
- :returns: The position ``(0-7)`` of the set day
- """
- self.logger.log(level, self.logprefix +
- "setting holidays file entry for %s",
- day_id)
- if obj is None:
- obj = self.holidays_obj
- day = obj._days_map[str(day_id).lower()]
- days_set = obj.days_set
- if day['valid'] is None: # Fresh entry
- days_set.append(day)
- day['position'] = len(days_set) - 1
- elif day['valid'] is False: # Previously invalidated entry
- days_set.insert(day['position'], day)
- else:
- pass
- day['valid'] = True
- day['p'] = str(prime)
- day['np'] = str(nonprime)
- self.logger.debug("holidays_set_day(): changed day struct: " +
- str(day))
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- return day['position']
- def holidays_get_day(self, day_id, obj=None, level=logging.INFO):
- """
- :param obj: optional holidays object to be used instead
- of internal
- :param day_id: either a day's name or "all"
- :type day_id: str
- :returns: A copy of info about a day/all set days
- """
- self.logger.log(level, self.logprefix +
- "getting holidays file entry for " +
- day_id)
- if obj is None:
- obj = self.holidays_obj
- days_set = obj.days_set
- days_map = obj._days_map
- if day_id == "all":
- return days_set[:]
- else:
- return days_map[day_id].copy()
- def holidays_reposition_day(self, day_id, new_pos, apply=True, obj=None,
- level=logging.INFO):
- """
- Change position of a day ``(0-7)`` as it appears in the
- holidays file
- :param day_id: name of the day
- :type day_id: str
- :param new_pos: new position
- :param apply: to reflect the changes to file
- :type apply: bool
- :param obj: optional holidays object to be used instead
- of internal
- :returns: The new position of the day
- """
- self.logger.log(level, self.logprefix +
- "repositioning holidays file entry for " +
- day_id + " to position " + str(new_pos))
- if obj is None:
- obj = self.holidays_obj
- days_map = obj._days_map
- days_set = obj.days_set
- day = days_map[str(day_id).lower()]
- if new_pos == day['position']:
- return
- # We also want to update order of invalid days, so add them to
- # days_set temporarily
- invalid_days = []
- for name in days_map:
- if days_map[name]['valid'] is False:
- invalid_days.append(days_map[name])
- days_set += invalid_days
- # Sort the old list
- days_set.sort(key=itemgetter('position'))
- # Change position of 'day_id'
- day['position'] = new_pos
- days_set.remove(day)
- days_set.insert(new_pos, day)
- # Update the 'position' field
- for i in range(0, len(days_set)):
- days_set[i]['position'] = i
- # Remove invalid days from days_set
- len_days_set = len(days_set)
- days_set = [days_set[i] for i in range(0, len_days_set)
- if days_set[i] not in invalid_days]
- self.logger.debug("holidays_reposition_day(): List of days after " +
- " re-positioning " + str(day_id) + " is:\n" +
- str(days_set))
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- return new_pos
- def holidays_unset_day(self, day_id, apply=True, obj=None,
- level=logging.INFO):
- """
- Unset prime time values for a day
- :param day_id: day to unset (string)
- :type day_id: str
- :param apply: to reflect the changes to file
- :param obj: optional holidays object to be used instead
- of internal
- .. note:: we do not unset the 'valid' field here so the entry
- will still be displayed but without any values
- """
- self.logger.log(level, self.logprefix +
- "unsetting holidays file entry for " + day_id)
- if obj is None:
- obj = self.holidays_obj
- day = obj._days_map[str(day_id).lower()]
- day['p'] = ""
- day['np'] = ""
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- def holidays_invalidate_day(self, day_id, apply=True, obj=None,
- level=logging.INFO):
- """
- Remove a day's entry from the holidays file
- :param day_id: the day to remove (string)
- :type day_id: str
- :param apply: to reflect the changes to file
- :type apply: bool
- :param obj: optional holidays object to be used instead
- of internal
- """
- self.logger.log(level, self.logprefix +
- "invalidating holidays file entry for " +
- day_id)
- if obj is None:
- obj = self.holidays_obj
- days_map = obj._days_map
- days_set = obj.days_set
- day = days_map[str(day_id).lower()]
- day['valid'] = False
- days_set.remove(day)
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- def holidays_validate_day(self, day_id, apply=True, obj=None,
- level=logging.INFO):
- """
- Make valid a previously set day's entry
- :param day_id: the day to validate (string)
- :type day_id: str
- :param apply: to reflect the changes to file
- :type apply: bool
- :param obj: optional holidays object to be used instead
- of internal
- .. note:: The day will retain its previous position in
- the file
- """
- self.logger.log(level, self.logprefix +
- "validating holidays file entry for " +
- day_id)
- if obj is None:
- obj = self.holidays_obj
- days_map = obj._days_map
- days_set = obj.days_set
- day = days_map[str(day_id).lower()]
- if day in days_set: # do not insert a pre-existing day
- self.logger.debug("holidays_validate_day(): " +
- day_id + " is already valid!")
- return
- day['valid'] = True
- days_set.insert(day['position'], day)
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- def holidays_delete_entry(self, entry_type, idx=None, apply=True,
- obj=None, level=logging.INFO):
- """
- Delete ``one/all`` entries from holidays file
- :param entry_type: 'y':year, 'd':day, 'h':holiday or 'a': all
- :type entry_type: str
- :param idx: either a day of week (monday, tuesday etc.)
- or Julian date of a holiday
- :type idx: str or None
- :param apply: to reflect the changes to file
- :type apply: bool
- :param obj: optional holidays object to be used instead of
- internal
- :returns: False if entry_type is invalid, otherwise True
- .. note:: The day cannot be validated and will lose it's
- position in the file
- """
- self.logger.log(level, self.logprefix +
- "Deleting entries from holidays file")
- if obj is None:
- obj = self.holidays_obj
- days_map = obj._days_map
- days_set = obj.days_set
- holiday_list = obj.holidays
- year = obj.year
- if entry_type not in ['a', 'y', 'd', 'h']:
- return False
- if entry_type == 'y' or entry_type == 'a':
- self.logger.debug(self.logprefix +
- "deleting year entry from holidays file")
- # Delete year entry
- year['value'] = None
- year['valid'] = False
- if entry_type == 'd' or entry_type == 'a':
- # Delete one/all day entries
- num_days_to_delete = 1
- if entry_type == 'a':
- self.logger.debug(self.logprefix +
- "deleting all days from holidays file")
- num_days_to_delete = len(days_set)
- for i in range(0, num_days_to_delete):
- if (entry_type == 'd'):
- self.logger.debug(self.logprefix +
- "deleting " + str(idx) +
- " entry from holidays file")
- day = days_map[str(idx).lower()]
- else:
- day = days_set[0]
- day['p'] = None
- day['np'] = None
- day['valid'] = None
- day['position'] = None
- days_set.remove(day)
- if entry_type == 'd':
- # Correct 'position' field of every day
- for i in range(0, len(days_set)):
- days_set[i]['position'] = i
- if entry_type == 'h' or entry_type == 'a':
- # Delete one/all calendar holiday entries
- if entry_type == 'a':
- self.logger.debug(self.logprefix +
- "deleting all holidays from holidays file")
- del holiday_list[:]
- else:
- self.logger.debug(self.logprefix +
- "deleting holiday on " + str(idx) +
- " from holidays file")
- holiday_list.remove(str(idx))
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- return True
- def holidays_set_year(self, new_year="", apply=True, obj=None,
- level=logging.INFO):
- """
- Set the year value
- :param newyear: year value to set
- :type newyear: str
- :param apply: to reflect the changes to file
- :type apply: bool
- :param obj: optional holidays object to be used instead
- of internal
- """
- self.logger.log(level, self.logprefix +
- "setting holidays file year entry to " +
- str(new_year))
- if obj is None:
- obj = self.holidays_obj
- year = obj.year
- year['value'] = str(new_year)
- year['valid'] = True
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- def holidays_unset_year(self, apply=True, obj=None, level=logging.INFO):
- """
- Unset the year value
- :param apply: to reflect the changes to file
- :type apply: bool
- :param obj: optional holidays object to be used instead
- of internal
- """
- self.logger.log(level, self.logprefix +
- "unsetting holidays file year entry")
- if obj is None:
- obj = self.holidays_obj
- obj.year['value'] = ""
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- def holidays_get_year(self, obj=None, level=logging.INFO):
- """
- :param obj: optional holidays object to be used instead
- of internal
- :returns: The year entry of holidays file
- """
- self.logger.log(level, self.logprefix +
- "getting holidays file year entry")
- if obj is None:
- obj = self.holidays_obj
- year = obj.year
- return year.copy()
- def holidays_add_holiday(self, date=None, apply=True, obj=None,
- level=logging.INFO):
- """
- Add a calendar holiday to the holidays file
- :param date: Date value for the holiday
- :param apply: to reflect the changes to file
- :type apply: bool
- :param obj: optional holidays object to be used instead
- of internal
- """
- self.logger.log(level, self.logprefix +
- "adding holiday " + str(date) +
- " to holidays file")
- if obj is None:
- obj = self.holidays_obj
- holiday_list = obj.holidays
- if date is not None:
- holiday_list.append(str(date))
- else:
- pass
- self.logger.debug("holidays list after adding one: " +
- str(holiday_list))
- if apply:
- self.holidays_write_file(obj=obj, level=logging.DEBUG)
- def holidays_get_holidays(self, obj=None, level=logging.INFO):
- """
- :param obj: optional holidays object to be used instead
- of internal
- :returns: The list of holidays in holidays file
- """
- self.logger.log(level, self.logprefix +
- "retrieving list of holidays")
- if obj is None:
- obj = self.holidays_obj
- holiday_list = obj.holidays
- return holiday_list[:]
- def _holidays_process_content(self, content, obj=None):
- """
- Process a user provided list of holidays file content
- :param obj: optional holidays object to be used instead
- of internal
- """
- self.logger.debug("_holidays_process_content(): " +
- "Processing user provided holidays content:\n" +
- str(content))
- if obj is None:
- obj = self.holidays_obj
- days_map = obj._days_map
- year = obj.year
- holiday_list = obj.holidays
- days_set = obj.days_set
- self.holidays_delete_entry(
- 'a', apply=False, obj=obj, level=logging.DEBUG)
- if content is None:
- self.logger.debug("Holidays file was wiped out")
- return
- for line in content:
- entry = line.split()
- if len(entry) == 0:
- continue
- tag = entry[0].lower()
- if tag == "year": # initialize self.year
- year['valid'] = True
- if len(entry) > 1:
- year['value'] = entry[1]
- elif tag in days_map.keys(): # initialize self.<day>
- day = days_map[tag]
- day['valid'] = True
- days_set.append(day)
- day['position'] = len(days_set) - 1
- if len(entry) > 1:
- day['p'] = entry[1]
- if len(entry) > 2:
- day['np'] = entry[2]
- elif tag.isdigit(): # initialize self.holiday
- holiday_list.append(tag)
- else:
- pass
- def holidays_write_file(self, content=None, out_path=None,
- hup=True, obj=None, level=logging.INFO):
- """
- Write to the holidays file with content ``given/generated``
- :param hup: SIGHUP the scheduler after writing the holidays
- file
- :type hup: bool
- :param obj: optional holidays object to be used instead of
- internal
- """
- self.logger.log(level, self.logprefix +
- "Writing to the holidays file")
- if obj is None:
- obj = self.holidays_obj
- if out_path is None:
- out_path = self.holidays_file
- if content is not None:
- self._holidays_process_content(content, obj)
- else:
- content = str(obj)
- self.logger.debug("content being written:\n" + str(content))
- fn = self.du.create_temp_file(self.hostname, body=content)
- ret = self.du.run_copy(self.hostname, fn, out_path,
- preserve_permission=False, sudo=True)
- self.du.rm(self.hostname, fn)
- if ret['rc'] != 0:
- raise PbsSchedConfigError(rc=ret['rc'], rv=ret['out'],
- msg=('error applying holidays file' +
- ret['err']))
- if hup:
- rv = self.signal('-HUP')
- if not rv:
- raise PbsSchedConfigError(rc=1, rv=False,
- msg='error applying holidays file')
- return True
- def parse_dedicated_time(self, file=None):
- """
- Parse the dedicated_time file and populate dedicated times
- as both a string dedicated_time array of dictionaries defined
- as ``[{'from': datetime, 'to': datetime}, ...]`` as well as a
- dedicated_time_as_str array with a string representation of
- each entry
- :param file: optional file to parse. Defaults to the one under
- ``PBS_HOME/sched_priv``
- :type file: str or None
- :returns: The dedicated_time list of dictionaries or None on
- error.Return an empty array if dedicated time file
- is empty.
- """
- self.dedicated_time_as_str = []
- self.dedicated_time = []
- if file:
- dt_file = file
- elif self.dedicated_time_file:
- dt_file = self.dedicated_time_file
- else:
- dt_file = os.path.join(self.pbs_conf['PBS_HOME'], 'sched_priv',
- 'dedicated_time')
- try:
- lines = self.du.cat(self.hostname, dt_file, sudo=True)['out']
- if lines is None:
- return []
- for line in lines:
- if not line.startswith('#') and len(line) > 0:
- self.dedicated_time_as_str.append(line)
- (dtime_from, dtime_to) = self.utils.convert_dedtime(line)
- self.dedicated_time.append({'from': dtime_from,
- 'to': dtime_to})
- except:
- self.logger.error('error in parse_dedicated_time')
- return None
- return self.dedicated_time
- def clear_dedicated_time(self, hup=True):
- """
- Clear the dedicated time file
- """
- self.parse_dedicated_time()
- if ((len(self.dedicated_time) == 0) and
- (len(self.dedicated_time_as_str) == 0)):
- return True
- if self.dedicated_time:
- for d in self.dedicated_time:
- del d
- if self.dedicated_time_as_str:
- for d in self.dedicated_time_as_str:
- del d
- self.dedicated_time = []
- self.dedicated_time_as_str = []
- dt = "# FORMAT: MM/DD/YYYY HH:MM MM/DD/YYYY HH:MM"
- return self.add_dedicated_time(dt, hup=hup)
- def add_dedicated_time(self, as_str=None, start=None, end=None, hup=True):
- """
- Append a dedicated time entry. The function can be called
- in one of two ways, either by passing in start and end as
- time values, or by passing as_str, a string that gets
- appended to the dedicated time entries and formatted as
- follows, note that no check on validity of the format will
- be made the function uses strftime to parse the datetime
- and will fail if the strftime can not convert the string.
- ``MM/DD/YYYY HH:MM MM/DD/YYYY HH:MM``
- :returns: True on success and False otherwise
- """
- if self.dedicated_time is None:
- self.parse_dedicated_time()
- if start is not None and end is not None:
- dtime_from = time.strftime("%m/%d/%Y %H:%M", time.localtime(start))
- dtime_to = time.strftime("%m/%d/%Y %H:%M", time.localtime(end))
- dedtime = dtime_from + " " + dtime_to
- elif as_str is not None:
- (dtime_from, dtime_to) = self.utils.convert_dedtime(as_str)
- dedtime = as_str
- else:
- self.logger.warning("no dedicated from/to specified")
- return True
- for d in self.dedicated_time_as_str:
- if dedtime == d:
- if dtime_from is None or dtime_to is None:
- self.logger.info(self.logprefix +
- "dedicated time already defined")
- else:
- self.logger.info(self.logprefix +
- "dedicated time from " + dtime_from +
- " to " + dtime_to + " already defined")
- return True
- if dtime_from is not None and dtime_to is not None:
- self.logger.info(self.logprefix +
- "adding dedicated time " + dedtime)
- self.dedicated_time_as_str.append(dedtime)
- if dtime_from is not None and dtime_to is not None:
- self.dedicated_time.append({'from': dtime_from, 'to': dtime_to})
- try:
- fn = self.du.create_temp_file()
- with open(fn, "w") as fd:
- for l in self.dedicated_time_as_str:
- fd.write(l + '\n')
- ddfile = os.path.join(self.pbs_conf['PBS_HOME'], 'sched_priv',
- 'dedicated_time')
- self.du.run_copy(self.hostname, fn, ddfile, sudo=True,
- preserve_permission=False)
- os.remove(fn)
- except:
- raise PbsSchedConfigError(rc=1, rv=False,
- msg='error adding dedicated time')
- if hup:
- ret = self.signal('-HUP')
- if ret['rc'] != 0:
- raise PbsSchedConfigError(rc=1, rv=False,
- msg='error adding dedicated time')
- return True
- def terminate(self):
- self.signal('-KILL')
- def valgrind(self):
- """
- run scheduler instance through valgrind
- """
- if self.isUp():
- self.terminate()
- rv = CliUtils().check_bin('valgrind')
- if not rv:
- self.logger.error(self.logprefix + 'valgrind not available')
- return None
- cmd = ['valgrind']
- cmd += ["--log-file=" + os.path.join(tempfile.gettempdir(),
- 'schd.vlgrd')]
- cmd += [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbs_sched')]
- return self.du.run_cmd(self.hostname, cmd, sudo=True)
- def alloc_to_execvnode(self, chunks):
- """
- convert a resource allocation to an execvnode string representation
- """
- execvnode = []
- for chunk in chunks:
- execvnode += ["(" + chunk.vnode]
- for res, val in chunk.resources.items():
- execvnode += [":" + str(res) + "=" + str(val)]
- for vchk in chunk.vchunk:
- execvnode += ["+" + vchk.vnode]
- for res, val in vchk.resources():
- execvnode += [":" + str(res) + "=" + str(val)]
- execvnode += [")+"]
- if len(execvnode) != 0:
- ev = execvnode[len(execvnode) - 1]
- ev = ev[:-1]
- execvnode[len(execvnode) - 1] = ev
- return "".join(execvnode)
- def cycles(self, start=None, end=None, firstN=None, lastN=None):
- """
- Analyze scheduler log and return cycle information
- :param start: Optional setting of the start time to consider
- :param end: Optional setting of the end time to consider
- :param firstN: Optional setting to consider the given first
- N cycles
- :param lastN: Optional setting to consider only the given
- last N cycles
- """
- try:
- from ptl.utils.pbs_logutils import PBSSchedulerLog
- except:
- self.logger.error('error loading ptl.utils.pbs_logutils')
- return None
- if start is not None or end is not None:
- analyze_path = os.path.dirname(self.logfile)
- else:
- analyze_path = self.logfile
- sl = PBSSchedulerLog()
- sl.analyze(analyze_path, start, end, self.hostname)
- cycles = sl.cycles
- if cycles is None or len(cycles) == 0:
- return []
- if lastN is not None:
- return cycles[-lastN:]
- elif firstN is not None:
- return cycles[:firstN]
- return cycles
- def query_fairshare(self, name=None, id=None):
- """
- Parse fairshare data using ``pbsfs`` and populates
- fairshare_tree.If name or id are specified, return the data
- associated to that id.Otherwise return the entire fairshare
- tree
- """
- if self.has_diag:
- return None
- tree = FairshareTree()
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbsfs')]
- if self.sc_name != 'default':
- cmd += ['-I', self.sc_name]
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True, logerr=False)
- if ret['rc'] != 0:
- raise PbsFairshareError(rc=ret['rc'], rv=None,
- msg=str(ret['err']))
- pbsfs = ret['out']
- for p in pbsfs:
- m = self.fs_tag.match(p)
- if m:
- usage = int(m.group('Usage'))
- perc = float(m.group('Perc'))
- nm = m.group('name')
- cgrp = int(m.group('cgrp'))
- pid = int(m.group('Grp'))
- nd = tree.get_node(id=pid)
- if nd:
- pname = nd.parent_name
- else:
- pname = None
- # if an entity has a negative cgroup it should belong
- # to the unknown resource, we work around the fact that
- # PBS Pro (up to 13.0) sets this cgroup id to -1 by
- # reassigning it to 0
- # TODO: cleanup once PBS code is updated
- if cgrp < 0:
- cgrp = 0
- node = FairshareNode(name=nm,
- id=cgrp,
- parent_id=pid,
- parent_name=pname,
- nshares=int(m.group('Shares')),
- usage=usage,
- perc={'TREEROOT': perc})
- if perc:
- node.prio['TREEROOT'] = float(usage) / perc
- if nm == name or id == cgrp:
- return node
- tree.add_node(node, apply=False)
- # now that all nodes are known, update parent and child
- # relationship of the tree
- tree.update()
- for node in tree.nodes.values():
- pnode = node._parent
- while pnode is not None and pnode.id != 0:
- if pnode.perc['TREEROOT']:
- node.perc[pnode.name] = \
- (node.perc['TREEROOT'] * 100 / pnode.perc[
- 'TREEROOT'])
- if pnode.name in node.perc and node.perc[pnode.name]:
- node.prio[pnode.name] = (
- node.usage / node.perc[pnode.name])
- pnode = pnode._parent
- if name:
- n = tree.get_node(name)
- if n is None:
- raise PbsFairshareError(rc=1, rv=None,
- msg='Unknown entity ' + name)
- return n
- if id:
- n = tree.get_node(id=id)
- raise PbsFairshareError(rc=1, rv=None,
- msg='Unknown entity ' + str(id))
- return n
- return tree
- def set_fairshare_usage(self, name=None, usage=None):
- """
- Set the fairshare usage associated to a given entity.
- :param name: The entity to set the fairshare usage of
- :type name: str or :py:class:`~ptl.lib.pbs_testlib.PbsUser` or None
- :param usage: The usage value to set
- """
- if self.has_diag:
- return True
- if name is None:
- self.logger.error(self.logprefix + ' an entity name required')
- return False
- if isinstance(name, PbsUser):
- name = str(name)
- if usage is None:
- self.logger.error(self.logprefix + ' a usage is required')
- return False
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbsfs')]
- if self.sc_name is not 'default':
- cmd += ['-I', self.sc_name]
- cmd += ['-s', name, str(usage)]
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True)
- if ret['rc'] == 0:
- return True
- return False
- def decay_fairshare_tree(self):
- """
- Decay the fairshare tree through pbsfs
- """
- if self.has_diag:
- return True
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbsfs')]
- if self.sc_name is not 'default':
- cmd += ['-I', self.sc_name]
- cmd += ['-d']
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True)
- if ret['rc'] == 0:
- self.fairshare_tree = self.query_fairshare()
- return True
- return False
- def cmp_fairshare_entities(self, name1=None, name2=None):
- """
- Compare two fairshare entities. Wrapper of ``pbsfs -c e1 e2``
- :param name1: name of first entity to compare
- :type name1: str or :py:class:`~ptl.lib.pbs_testlib.PbsUser` or None
- :param name2: name of second entity to compare
- :type name2: str or :py:class:`~ptl.lib.pbs_testlib.PbsUser` or None
- :returns: the name of the entity of higher priority or None on error
- """
- if self.has_diag:
- return None
- if name1 is None or name2 is None:
- self.logger.erro(self.logprefix + 'two fairshare entity names ' +
- 'required')
- return None
- if isinstance(name1, PbsUser):
- name1 = str(name1)
- if isinstance(name2, PbsUser):
- name2 = str(name2)
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbsfs')]
- if self.sc_name is not 'default':
- cmd += ['-I', self.sc_name]
- cmd += ['-c', name1, name2]
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True)
- if ret['rc'] == 0:
- return ret['out'][0]
- return None
- def parse_resource_group(self, hostname=None, resource_group=None):
- """
- Parse the Scheduler's ``resource_group`` file
- :param hostname: The name of the host from which to parse
- resource_group
- :type hostname: str or None
- :param resource_group: The path to a resource_group file
- :type resource_group: str or None
- :returns: A fairshare tree
- """
- if hostname is None:
- hostname = self.hostname
- # if resource_group is None:
- resource_group = self.resource_group_file
- # if has_diag is True acces to sched_priv may not require su privilege
- ret = self.du.cat(hostname, resource_group, sudo=(not self.has_diag))
- if ret['rc'] != 0:
- self.logger.error(hostname + ' error reading ' + resource_group)
- tree = FairshareTree(hostname, resource_group)
- root = FairshareNode('root', -1, parent_id=0, nshares=100)
- tree.add_node(root, apply=False)
- lines = ret['out']
- for line in lines:
- line = line.strip()
- if not line.startswith("#") and len(line) > 0:
- # could have 5th column but we only need the first 4
- (name, id, parent, nshares) = line.split()[:4]
- node = FairshareNode(name, id, parent_name=parent,
- nshares=nshares)
- tree.add_node(node, apply=False)
- tree.update()
- return tree
- def add_to_resource_group(self, name, fairshare_id, parent, nshares,
- validate=True):
- """
- Add an entry to the resource group file
- :param name: The name of the entity to add
- :type name: str or :py:class:`~ptl.lib.pbs_testlib.PbsUser`
- :param fairshare_id: The numeric identifier of the entity to add
- :type fairshare_id: int
- :param parent: The name of the parent group
- :type parent: str
- :param nshares: The number of shares associated to the entity
- :type nshares: int
- :param validate: if True (the default), validate the
- configuration settings.
- :type validate: bool
- """
- if self.resource_group is None:
- self.resource_group = self.parse_resource_group(
- self.hostname, self.resource_group_file)
- if not self.resource_group:
- self.resource_group = FairshareTree(
- self.hostname, self.resource_group_file)
- if isinstance(name, PbsUser):
- name = str(name)
- reconfig_time = int(time.time())
- rc = self.resource_group.create_node(name, fairshare_id,
- parent_name=parent,
- nshares=nshares)
- if validate:
- self.get_pid()
- self.signal('-HUP')
- try:
- self.log_match("Sched;reconfigure;Scheduler is reconfiguring",
- n=10, starttime=reconfig_time)
- self.log_match("fairshare;resgroup: error ", n=10,
- starttime=reconfig_time, existence=False,
- max_attempts=2)
- except PtlLogMatchError:
- _msg = 'Error in validating resource_group changes'
- raise PbsSchedConfigError(rc=1, rv=False,
- msg=_msg)
- return rc
- def job_formula(self, jobid=None, starttime=None, max_attempts=5):
- """
- Extract formula value out of scheduler log
- :param jobid: Optional, the job identifier for which to get
- the formula.
- :type jobid: str or int
- :param starttime: The time at which to start parsing the
- scheduler log
- :param max_attempts: The number of attempts to search for
- formula in the logs
- :type max_attempts: int
- :returns: If jobid is specified, return the formula value
- associated to that job if no jobid is specified,
- returns a dictionary mapping job ids to formula
- """
- if jobid is None:
- jobid = "(?P<jobid>.*)"
- _alljobs = True
- else:
- if isinstance(jobid, int):
- jobid = str(jobid)
- _alljobs = False
- formula_pat = (".*Job;" + jobid +
- ".*;Formula Evaluation = (?P<fval>.*)")
- rv = self.log_match(formula_pat, regexp=True, starttime=starttime,
- n='ALL', allmatch=True, max_attempts=5)
- ret = {}
- if rv:
- for _, l in rv:
- m = re.match(formula_pat, l)
- if m:
- if _alljobs:
- jobid = m.group('jobid')
- ret[jobid] = float(m.group('fval').strip())
- if not _alljobs:
- if jobid in ret:
- return ret[jobid]
- else:
- return
- return ret
- class FairshareTree(object):
- """
- Object representation of the Scheduler's resource_group
- file and pbsfs data
- :param hostname: Hostname of the machine
- :type hostname: str
- """
- du = DshUtils()
- def __init__(self, hostname=None, resource_group=None):
- self.logger = logging.getLogger(__name__)
- self.hostname = hostname
- self.resource_group = resource_group
- self.nodes = {}
- self.root = None
- self._next_id = -1
- def update_resource_group(self):
- if self.resource_group:
- fn = self.du.create_temp_file(body=self.__str__())
- ret = self.du.run_copy(self.hostname, fn, self.resource_group,
- preserve_permission=False, sudo=True)
- os.remove(fn)
- if ret['rc'] != 0:
- raise PbsFairshareError(rc=1, rv=False,
- msg='error updating resource group')
- return True
- def update(self):
- for node in self.nodes.values():
- if node._parent is None:
- pnode = self.get_node(id=node.parent_id)
- if pnode:
- node._parent = pnode
- if node not in pnode._child:
- pnode._child.append(node)
- def _add_node(self, node):
- if node.name == 'TREEROOT' or node.name == 'root':
- self.root = node
- self.nodes[node.name] = node
- if node.parent_name in self.nodes:
- self.nodes[node.parent_name]._child.append(node)
- node._parent = self.nodes[node.parent_name]
- def add_node(self, node, apply=True):
- """
- add node to the fairshare tree
- """
- self._add_node(node)
- if apply:
- return self.update_resource_group()
- return True
- def create_node(self, name, id, parent_name, nshares):
- """
- Add an entry to the ``resource_group`` file
- :param name: The name of the entity to add
- :type name: str
- :param id: The uniqe numeric identifier of the entity
- :type id: int
- :param parent: The name of the parent/group of the entity
- :type parent: str
- :param nshares: The number of shares assigned to this entity
- :type nshares: int
- :returns: True on success, False otherwise
- """
- if name in self.nodes:
- self.logger.warning('fairshare: node ' + name + ' already defined')
- return True
- self.logger.info('creating tree node: ' + name)
- node = FairshareNode(name, id, parent_name=parent_name,
- nshares=nshares)
- self._add_node(node)
- return self.update_resource_group()
- def get_node(self, name=None, id=None):
- """
- Return a node of the fairshare tree identified by either
- name or id.
- :param name: The name of the entity to query
- :type name: str or None
- :param id: The id of the entity to query
- :returns: The fairshare information of the entity when
- found, if not, returns None
- .. note:: The name takes precedence over the id.
- """
- for node in self.nodes.values():
- if name is not None and node.name == name:
- return node
- if id is not None and node.id == id:
- return node
- return None
- def __batch_status__(self):
- """
- Convert fairshare tree object to a batch status format
- """
- dat = []
- for node in self.nodes.values():
- if node.name == 'root':
- continue
- einfo = {}
- einfo['cgroup'] = node.id
- einfo['id'] = node.name
- einfo['group'] = node.parent_id
- einfo['nshares'] = node.nshares
- if len(node.prio) > 0:
- p = []
- for k, v in node.prio.items():
- p += ["%s:%d" % (k, int(v))]
- einfo['penalty'] = ", ".join(p)
- einfo['usage'] = node.usage
- if node.perc:
- p = []
- for k, v in node.perc.items():
- p += ["%s:%.3f" % (k, float(v))]
- einfo['shares_perc'] = ", ".join(p)
- ppnode = self.get_node(id=node.parent_id)
- if ppnode:
- ppname = ppnode.name
- ppid = ppnode.id
- else:
- ppnode = self.get_node(name=node.parent_name)
- if ppnode:
- ppname = ppnode.name
- ppid = ppnode.id
- else:
- ppname = ''
- ppid = None
- einfo['parent'] = "%s (%s) " % (str(ppid), ppname)
- dat.append(einfo)
- return dat
- def get_next_id(self):
- self._next_id -= 1
- return self._next_id
- def __repr__(self):
- return self.__str__()
- def _dfs(self, node, dat):
- if node.name != 'root':
- s = []
- if node.name is not None:
- s += [node.name]
- if node.id is not None:
- s += [str(node.id)]
- if node.parent_name is not None:
- s += [node.parent_name]
- if node.nshares is not None:
- s += [str(node.nshares)]
- if node.usage is not None:
- s += [str(node.usage)]
- dat.append("\t".join(s))
- for n in node._child:
- self._dfs(n, dat)
- def __str__(self):
- dat = []
- if self.root:
- self._dfs(self.root, dat)
- if len(dat) > 0:
- dat += ['\n']
- return "\n".join(dat)
- class FairshareNode(object):
- """
- Object representation of the fairshare data as queryable through
- the command ``pbsfs``.
- :param name: Name of fairshare node
- :type name: str or None
- :param nshares: Number of shares
- :type nshares: int or None
- :param usage: Fairshare usage
- :param perc: Percentage the entity has of the tree
- """
- def __init__(self, name=None, id=None, parent_name=None, parent_id=None,
- nshares=None, usage=None, perc=None):
- self.name = name
- self.id = id
- self.parent_name = parent_name
- self.parent_id = parent_id
- self.nshares = nshares
- self.usage = usage
- self.perc = perc
- self.prio = {}
- self._parent = None
- self._child = []
- def __str__(self):
- ret = []
- if self.name is not None:
- ret.append(self.name)
- if self.id is not None:
- ret.append(str(self.id))
- if self.parent_name is not None:
- ret.append(str(self.parent_name))
- if self.nshares is not None:
- ret.append(str(self.nshares))
- if self.usage is not None:
- ret.append(str(self.usage))
- if self.perc is not None:
- ret.append(str(self.perc))
- return "\t".join(ret)
- class MoM(PBSService):
- """
- Container for MoM properties.
- Provides various MoM operations, such as creation, insertion,
- deletion of vnodes.
- :param name: The hostname of the server. Defaults to calling
- pbs_default()
- :type name: str or None
- :param attrs: Dictionary of attributes to set, these will
- override defaults.
- :type attrs: Dictionary
- :param pbsconf_file: path to config file to parse for
- ``PBS_HOME``, ``PBS_EXEC``, etc
- :type pbsconf_file: str or None
- :param diagmap: A dictionary of PBS objects ``(node,server,etc)``
- to mapped files from PBS diag directory
- :type diagmap: Dictionary
- :param diag: path to PBS diag directory (This will overrides
- diagmap)
- :type diag: str or None
- :param server: A PBS server instance to which this mom is associated
- :param db_acccess: set to either file containing credentials to DB
- access or dictionary containing
- {'dbname':...,'user':...,'port':...}
- :type db_access: str or dictionary
- """
- dflt_attributes = {}
- conf_to_cmd_map = {'PBS_MOM_SERVICE_PORT': '-M',
- 'PBS_MANAGER_SERVICE_PORT': '-R',
- 'PBS_HOME': '-d'}
- def __init__(self, name=None, attrs={}, pbsconf_file=None, diagmap={},
- diag=None, server=None, db_access=None):
- self.logger = logging.getLogger(__name__)
- if server is not None:
- self.server = server
- if diag is None and self.server.diag is not None:
- diag = self.server.diag
- if (len(diagmap) == 0) and (len(self.server.diagmap) != 0):
- diagmap = self.server.diagmap
- else:
- self.server = Server(name, pbsconf_file=pbsconf_file,
- db_access=db_access, diag=diag,
- diagmap=diagmap)
- PBSService.__init__(self, name, attrs, self.dflt_attributes,
- pbsconf_file, diag=diag, diagmap=diagmap)
- _m = ['mom ', self.shortname]
- if pbsconf_file is not None:
- _m += ['@', pbsconf_file]
- _m += [': ']
- self.logprefix = "".join(_m)
- self.pi = PBSInitServices(hostname=self.hostname,
- conf=self.pbs_conf_file)
- self.configd = os.path.join(self.pbs_conf['PBS_HOME'], 'mom_priv',
- 'config.d')
- self.config = {}
- if self.platform == 'cray' or self.platform == 'craysim':
- usecp = os.path.realpath('/home')
- if self.platform == 'cray':
- if os.path.exists('/opt/cray/alps/default/bin/apbasil'):
- alps_client = '/opt/cray/alps/default/bin/apbasil'
- else:
- alps_client = self.du.which(exe='apbasil')
- else:
- alps_client = "/opt/alps/apbasil.sh"
- self.dflt_config = {'$clienthost': self.server.hostname,
- '$vnodedef_additive': 0,
- '$alps_client': alps_client,
- '$usecp': '*:%s %s' % (usecp, usecp)}
- else:
- self.dflt_config = {'$clienthost': self.server.hostname}
- self.version = None
- self._is_cpuset_mom = None
- def isUp(self):
- """
- Check for PBS mom up
- """
- return super(MoM, self)._isUp(self)
- def signal(self, sig):
- """
- Send signal to PBS mom
- """
- self.logger.info(self.logprefix + 'sent signal ' + sig)
- return super(MoM, self)._signal(sig, inst=self)
- def get_pid(self):
- """
- Get the PBS mom pid
- """
- return super(MoM, self)._get_pid(inst=self)
- def all_instance_pids(self):
- """
- Get all pids of a instance
- """
- return super(MoM, self)._all_instance_pids(inst=self)
- def start(self, args=None, launcher=None):
- """
- Start the PBS mom
- :param args: Arguments to start the mom
- :type args: str or None
- :param launcher: Optional utility to invoke the launch of the service
- :type launcher: str or list or None
- """
- if args is not None or launcher is not None:
- return super(MoM, self)._start(inst=self, args=args,
- cmd_map=self.conf_to_cmd_map,
- launcher=launcher)
- else:
- try:
- rv = self.pi.start_mom()
- self._update_pid(self)
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg)
- return rv
- def stop(self, sig=None):
- """
- Stop the PBS mom
- :param sig: Signal to stop the PBS mom
- :type sig: str
- """
- if sig is not None:
- self.logger.info(self.logprefix + 'stopping MoM on host ' +
- self.hostname)
- return super(MoM, self)._stop(sig, inst=self)
- else:
- try:
- self.pi.stop_mom()
- self.pid = None
- except PbsInitServicesError as e:
- raise PbsServiceError(rc=e.rc, rv=e.rv, msg=e.msg)
- return True
- def restart(self):
- """
- Restart the PBS mom
- """
- if self.isUp():
- if not self.stop():
- return False
- return self.start()
- def log_match(self, msg=None, id=None, n=50, tail=True, allmatch=False,
- regexp=False, max_attempts=None, interval=None,
- starttime=None, endtime=None, level=logging.INFO,
- existence=True):
- """
- Match given ``msg`` in given ``n`` lines of MoM log
- :param msg: log message to match, can be regex also when
- ``regexp`` is True
- :type msg: str
- :param id: The id of the object to trace. Only used for
- tracejob
- :type id: str
- :param n: 'ALL' or the number of lines to search through,
- defaults to 50
- :type n: str or int
- :param tail: If true (default), starts from the end of
- the file
- :type tail: bool
- :param allmatch: If True all matching lines out of then
- parsed are returned as a list. Defaults
- to False
- :type allmatch: bool
- :param regexp: If true msg is a Python regular expression.
- Defaults to False
- :type regexp: bool
- :param max_attempts: the number of attempts to make to find
- a matching entry
- :type max_attempts: int
- :param interval: the interval between attempts
- :type interval: int
- :param starttime: If set ignore matches that occur before
- specified time
- :type starttime: int
- :param endtime: If set ignore matches that occur after
- specified time
- :type endtime: int
- :param level: The logging level, defaults to INFO
- :type level: int
- :param existence: If True (default), check for existence of
- given msg, else check for non-existence of
- given msg.
- :type existence: bool
- :return: (x,y) where x is the matching line
- number and y the line itself. If allmatch is True,
- a list of tuples is returned.
- :rtype: tuple
- :raises PtlLogMatchError:
- When ``existence`` is True and given
- ``msg`` is not found in ``n`` line
- Or
- When ``existence`` is False and given
- ``msg`` found in ``n`` line.
- .. note:: The matching line number is relative to the record
- number, not the absolute line number in the file.
- """
- return self._log_match(self, msg, id, n, tail, allmatch, regexp,
- max_attempts, interval, starttime, endtime,
- level, existence)
- def pbs_version(self):
- """
- Get the PBS version
- """
- if self.version:
- return self.version
- exe = os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbs_mom')
- version = self.du.run_cmd(self.hostname,
- [exe, '--version'], sudo=True)['out']
- if version:
- self.logger.debug(version)
- # in some cases pbs_mom --version may return multiple lines, we
- # only care about the one that carries pbs_version information
- for ver in version:
- if 'pbs_version' in ver:
- version = ver.split('=')[1].strip()
- break
- else:
- version = self.log_match('pbs_version', tail=False)
- if version:
- version = version[1].strip().split('=')[1].strip()
- else:
- version = "unknown"
- self.version = LooseVersion(version)
- return self.version
- def delete_vnodes(self):
- rah = ATTR_rescavail + '.host'
- rav = ATTR_rescavail + '.vnode'
- a = {rah: self.hostname, rav: None}
- try:
- _vs = self.server.status(HOST, a, id=self.hostname)
- except PbsStatusError:
- try:
- _vs = self.server.status(HOST, a, id=self.shortname)
- except PbsStatusError as e:
- err_msg = e.msg[0].rstrip()
- if (err_msg.endswith('Server has no node list') or
- err_msg.endswith('Unknown node')):
- _vs = []
- else:
- raise e
- vs = []
- for v in _vs:
- if v[rav].split('.')[0] != v[rah].split('.')[0]:
- vs.append(v['id'])
- if len(vs) > 0:
- self.server.manager(MGR_CMD_DELETE, VNODE, id=vs)
- def revert_to_defaults(self, delvnodedefs=True):
- """
- 1. ``Revert MoM configuration to defaults.``
- 2. ``Remove epilogue and prologue``
- 3. ``Delete all vnode definitions
- HUP MoM``
- :param delvnodedefs: if True (the default) delete all vnode
- definitions and restart the MoM
- :type delvnodedefs: bool
- :returns: True on success and False otherwise
- """
- self.logger.info(self.logprefix +
- 'reverting configuration to defaults')
- restart = False
- if not self.has_diag:
- self.delete_pelog()
- if delvnodedefs and self.has_vnode_defs():
- restart = True
- if not self.delete_vnode_defs():
- return False
- self.delete_vnodes()
- if cmp(self.config, self.dflt_config) != 0:
- self.apply_config(self.dflt_config, hup=False, restart=False)
- if restart:
- self.restart()
- else:
- self.signal('-HUP')
- return self.isUp()
- return True
- def save_configuration(self, outfile, mode='a'):
- """
- Save a MoM ``mom_priv/config``
- :param outfile: the output file to which onfiguration is
- saved
- :type outfile: str
- :param mode: the mode in which to open outfile to save
- configuration.
- :type mode: str
- :returns: True on success, False on error
- .. note:: first object being saved should open this file
- with 'w' and subsequent calls from other objects
- should save with mode 'a' or 'a+'. Defaults to a+
- """
- conf = {}
- mconf = {MGR_OBJ_NODE: conf}
- mpriv = os.path.join(self.pbs_conf['PBS_HOME'], 'mom_priv')
- cf = os.path.join(mpriv, 'config')
- self._save_config_file(conf, cf)
- if os.path.isdir(os.path.join(mpriv, 'config.d')):
- for f in os.listdir(os.path.join(mpriv, 'config.d')):
- self._save_config_file(conf,
- os.path.join(mpriv, 'config.d', f))
- try:
- f = open(outfile, mode)
- cPickle.dump(mconf, f)
- f.close()
- except:
- self.logger.error('error saving configuration to ' + outfile)
- return False
- return True
- def load_configuration(self, infile):
- """
- load configuration from saved file infile
- """
- self._load_configuration(infile, MGR_OBJ_NODE)
- def is_cray(self):
- """
- Returns True if the version of PBS used was built for Cray platforms
- """
- try:
- self.log_match("alps_client", n='ALL', tail=False, max_attempts=1)
- except PtlLogMatchError:
- return False
- else:
- return True
- def is_cpuset_mom(self):
- """
- Check for cpuset mom
- """
- if self._is_cpuset_mom is not None:
- return self._is_cpuset_mom
- raa = ATTR_rescavail + '.arch'
- a = {raa: None}
- try:
- rv = self.server.status(NODE, a, id=self.shortname)
- except PbsStatusError:
- try:
- rv = self.server.status(NODE, a, id=self.hostname)
- except PbsStatusError as e:
- if e.msg[0].endswith('Server has no node list'):
- return False
- else:
- raise e
- if rv[0][raa] == 'linux_cpuset':
- self._is_cpuset_mom = True
- else:
- self._is_cpuset_mom = False
- return self._is_cpuset_mom
- def create_vnode_def(self, name, attrs={}, numnodes=1, sharednode=True,
- pre='[', post=']', usenatvnode=False, attrfunc=None,
- vnodes_per_host=1):
- """
- Create a vnode definition string representation
- :param name: The prefix for name of vnode to create,
- name of vnode will be prefix + pre + <num> +
- post
- :type name: str
- :param attrs: Dictionary of attributes to set on each vnode
- :type attrs: Dictionary
- :param numnodes: The number of vnodes to create
- :type numnodes: int
- :param sharednode: If true vnodes are shared on a host
- :type sharednode: bool
- :param pre: The symbol preceding the numeric value of that
- vnode.
- :type pre: str
- :param post: The symbol following the numeric value of that
- vnode.
- :type post: str
- :param usenatvnode: use the natural vnode as the first vnode
- to allocate this only makes sense
- starting with PBS 11.3 when natural
- vnodes are reported as a allocatable
- :type usenatvnode: bool
- :param attrfunc: function to customize the attributes,
- signature is (name, numnodes, curnodenum,
- attrs), must return a dict that contains
- new or modified attrs that will be added to
- the vnode def. The function is called once
- per vnode being created, it does not modify
- attrs itself across calls.
- :param vnodes_per_host: number of vnodes per host
- :type vnodes_per_host: int
- :returns: A string representation of the vnode definition
- file
- """
- sethost = False
- attribs = attrs.copy()
- if not sharednode and 'resources_available.host' not in attrs:
- sethost = True
- if attrfunc is None:
- customattrs = attribs
- vdef = ["$configversion 2"]
- # altering the natural vnode information
- if numnodes == 0:
- for k, v in attribs.items():
- vdef += [name + ": " + str(k) + "=" + str(v)]
- else:
- if usenatvnode:
- if attrfunc:
- customattrs = attrfunc(name, numnodes, "", attribs)
- for k, v in customattrs.items():
- vdef += [self.shortname + ": " + str(k) + "=" + str(v)]
- # account for the use of the natural vnode
- numnodes -= 1
- else:
- # ensure that natural vnode is not allocatable by the scheduler
- vdef += [self.shortname + ": resources_available.ncpus=0"]
- vdef += [self.shortname + ": resources_available.mem=0"]
- for n in xrange(numnodes):
- vnid = name + pre + str(n) + post
- if sethost:
- if vnodes_per_host > 1:
- if n % vnodes_per_host == 0:
- _nid = vnid
- else:
- _nid = name + pre + str(n - n % vnodes_per_host) + post
- attribs['resources_available.host'] = _nid
- else:
- attribs['resources_available.host'] = vnid
- if attrfunc:
- customattrs = attrfunc(vnid, numnodes, n, attribs)
- for k, v in customattrs.items():
- vdef += [vnid + ": " + str(k) + "=" + str(v)]
- if numnodes == 0:
- nn = 1
- else:
- nn = numnodes
- if numnodes > 1:
- vnn_msg = ' vnodes '
- else:
- vnn_msg = ' vnode '
- self.logger.info(self.logprefix + 'created ' + str(nn) +
- vnn_msg + name + ' with attr ' +
- str(attribs) + ' on host ' + self.hostname)
- vdef += ["\n"]
- del attribs
- return "\n".join(vdef)
- def parse_config(self):
- """
- Parse mom config file into a dictionary of configuration
- options.
- :returns: A dictionary of configuration options on success,
- and None otherwise
- """
- try:
- mconf = os.path.join(self.pbs_conf['PBS_HOME'], 'mom_priv',
- 'config')
- ret = self.du.cat(self.hostname, mconf, sudo=True)
- if ret['rc'] != 0:
- self.logger.error('error parsing configuration file')
- return None
- self.config = {}
- lines = ret['out']
- for line in lines:
- (k, v) = line.split(' ', 1)
- if k in self.config:
- if isinstance(self.config[k], list):
- self.config[k].append(v)
- else:
- self.config[k] = [self.config[k], v]
- else:
- self.config[k] = v
- except:
- self.logger.error('error in parse_config')
- return None
- return self.config
- def add_config(self, conf={}, hup=True):
- """
- Add config options to mom_priv_config.
- :param conf: The configurations to add to ``mom_priv/config``
- :type conf: Dictionary
- :param hup: If True (default) ``HUP`` the MoM
- :type hup: bool
- :returns: True on success and False otherwise
- """
- doconfig = False
- if not self.config:
- self.parse_config()
- mc = self.config
- if mc is None:
- mc = {}
- for k, v in conf.items():
- if k in mc and (mc[k] == v or (isinstance(v, list) and
- mc[k] in v)):
- self.logger.debug(self.logprefix + 'config ' + k +
- ' already set to ' + str(v))
- continue
- else:
- doconfig = True
- break
- if not doconfig:
- return True
- self.logger.info(self.logprefix + "config " + str(conf))
- return self.apply_config(conf, hup)
- def unset_mom_config(self, name, hup=True):
- """
- Delete a mom_config entry
- :param name: The entry to remove from ``mom_priv/config``
- :type name: String
- :param hup: if True (default) ``HUP`` the MoM
- :type hup: bool
- :returns: True on success and False otherwise
- """
- mc = self.parse_config()
- if mc is None or name not in mc:
- return True
- self.logger.info(self.logprefix + "unsetting config " + name)
- del mc[name]
- return self.apply_config(mc, hup)
- def apply_config(self, conf={}, hup=True, restart=False):
- """
- Apply configuration options to MoM.
- :param conf: A dictionary of configuration options to apply
- to MoM
- :type conf: Dictionary
- :param hup: If True (default) , HUP the MoM to apply the
- configuration
- :type hup: bool
- :returns: True on success and False otherwise.
- """
- self.config = dict(self.config.items() + conf.items())
- try:
- fn = self.du.create_temp_file()
- with open(fn, 'w+') as f:
- for k, v in self.config.items():
- if isinstance(v, list):
- for eachprop in v:
- f.write(str(k) + ' ' + str(eachprop) + '\n')
- else:
- f.write(str(k) + ' ' + str(v) + '\n')
- dest = os.path.join(
- self.pbs_conf['PBS_HOME'], 'mom_priv', 'config')
- self.du.run_copy(self.hostname, fn, dest,
- preserve_permission=False, sudo=True)
- os.remove(fn)
- except:
- raise PbsMomConfigError(rc=1, rv=False,
- msg='error processing add_config')
- if restart:
- return self.restart()
- elif hup:
- return self.signal('-HUP')
- return True
- def get_vnode_def(self, vnodefile=None):
- """
- :returns: A vnode def file as a single string
- """
- if vnodefile is None:
- return None
- f = open(vnodefile)
- lines = f.readlines()
- f.close()
- return "".join(lines)
- def insert_vnode_def(self, vdef, fname=None, additive=False, restart=True):
- """
- Insert and enable a vnode definition. Root privilege
- is required
- :param vdef: The vnode definition string as created by
- create_vnode_def
- :type vdef: str
- :param fname: The filename to write the vnode def string to
- :type fname: str or None
- :param additive: If True, keep all other vnode def files
- under config.d Default is False
- :type additive: bool
- :param delete: If True, delete all nodes known to the server.
- Default is True
- :type delete: bool
- :param restart: If True, restart the MoM. Default is True
- :type restart: bool
- """
- try:
- fn = self.du.create_temp_file(self.hostname, body=vdef)
- except:
- raise PbsMomConfigError(rc=1, rv=False,
- msg="Failed to insert vnode definition")
- if fname is None:
- fname = 'pbs_vnode_' + str(int(time.time())) + '.def'
- if not additive:
- self.delete_vnode_defs()
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbs_mom')]
- cmd += ['-s', 'insert', fname, fn]
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True, logerr=False,
- level=logging.INFOCLI)
- self.du.rm(hostname=self.hostname, path=fn, force=True)
- if ret['rc'] != 0:
- raise PbsMomConfigError(rc=1, rv=False, msg="\n".join(ret['err']))
- msg = self.logprefix + 'inserted vnode definition file '
- msg += fname + ' on host: ' + self.hostname
- self.logger.info(msg)
- if restart:
- self.restart()
- def has_vnode_defs(self):
- """
- Check for vnode definition(s)
- """
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbs_mom')]
- cmd += ['-s', 'list']
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True, logerr=False,
- level=logging.INFOCLI)
- if ret['rc'] == 0:
- files = [x for x in ret['out'] if not x.startswith('PBS')]
- if len(files) > 0:
- return True
- else:
- return False
- else:
- return False
- def delete_vnode_defs(self, vdefname=None):
- """
- delete vnode definition(s) on this MoM
- :param vdefname: name of a vnode definition file to delete,
- if None all vnode definitions are deleted
- :type vdefname: str
- :returns: True if delete succeed otherwise False
- """
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin', 'pbs_mom')]
- cmd += ['-s', 'list']
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True, logerr=False,
- level=logging.INFOCLI)
- if ret['rc'] != 0:
- return False
- rv = True
- if len(ret['out']) > 0:
- for vnodedef in ret['out']:
- vnodedef = vnodedef.strip()
- if (vnodedef == vdefname) or vdefname is None:
- if vnodedef.startswith('PBS'):
- continue
- cmd = [os.path.join(self.pbs_conf['PBS_EXEC'], 'sbin',
- 'pbs_mom')]
- cmd += ['-s', 'remove', vnodedef]
- ret = self.du.run_cmd(self.hostname, cmd, sudo=True,
- logerr=False, level=logging.INFOCLI)
- if ret['rc'] != 0:
- return False
- else:
- rv = True
- return rv
- def has_pelog(self, filename=None):
- """
- Check for prologue and epilogue
- """
- _has_pro = False
- _has_epi = False
- phome = self.pbs_conf['PBS_HOME']
- prolog = os.path.join(phome, 'mom_priv', 'prologue')
- epilog = os.path.join(phome, 'mom_priv', 'epilogue')
- if self.du.isfile(self.hostname, path=prolog, sudo=True):
- _has_pro = True
- if filename == 'prologue':
- return _has_pro
- if self.du.isfile(self.hostname, path=epilog, sudo=True):
- _has_epi = True
- if filename == 'epilogue':
- return _has_pro
- if _has_epi or _has_pro:
- return True
- return False
- def has_prologue(self):
- """
- Check for prologue
- """
- return self.has_pelog('prolouge')
- def has_epilogue(self):
- """
- Check for epilogue
- """
- return self.has_pelog('epilogue')
- def delete_pelog(self):
- """
- Delete any prologue and epilogue files that may have been
- defined on this MoM
- """
- phome = self.pbs_conf['PBS_HOME']
- prolog = os.path.join(phome, 'mom_priv', 'prologue')
- epilog = os.path.join(phome, 'mom_priv', 'epilogue')
- ret = self.du.rm(self.hostname, epilog, force=True,
- sudo=True, logerr=False)
- if ret:
- ret = self.du.rm(self.hostname, prolog, force=True,
- sudo=True, logerr=False)
- if not ret:
- self.logger.error('problem deleting prologue/epilogue')
- # we don't bail because the problem may be that files did not
- # exist. Let tester fix the issue
- return ret
- def create_pelog(self, body=None, src=None, filename=None):
- """
- create ``prologue`` and ``epilogue`` files, functionality
- accepts either a body of the script or a source file.
- :returns: True on success and False on error
- """
- if self.has_diag:
- _msg = 'MoM is in loaded from diag so bypassing pelog creation'
- self.logger.info(_msg)
- return False
- if (src is None and body is None) or (filename is None):
- self.logger.error('file and body of script are required')
- return False
- pelog = os.path.join(self.pbs_conf['PBS_HOME'], 'mom_priv', filename)
- self.logger.info(self.logprefix +
- ' creating ' + filename + ' with body\n' + '---')
- if body is not None:
- self.logger.info(body)
- src = self.du.create_temp_file(prefix='pbs-pelog', body=body)
- elif src is not None:
- _b = open(src)
- self.logger.info("\n".join(_b.readlines()))
- _b.close()
- self.logger.info('---')
- ret = self.du.run_copy(self.hostname, src, pelog,
- preserve_permission=False, sudo=True)
- if body is not None:
- os.remove(src)
- if ret['rc'] != 0:
- self.logger.error('error creating pelog ')
- return False
- ret = self.du.chown(self.hostname, path=pelog, uid=0, gid=0, sudo=True,
- logerr=False)
- if not ret:
- self.logger.error('error chowning pelog to root')
- return False
- ret = self.du.chmod(self.hostname, path=pelog, mode=0755, sudo=True)
- return ret
- def prologue(self, body=None, src=None):
- """
- create prologue
- """
- return self.create_pelog(body, src, 'prologue')
- def epilogue(self, body=None, src=None):
- """
- Create epilogue
- """
- return self.create_pelog(body, src, 'epilogue')
- def action(self, act, script):
- """
- Define action script. Not currently implemented
- """
- pass
- class Hook(PBSObject):
- """
- PBS hook objects. Holds attributes information and pointer
- to server
- :param name: Hook name
- :type name: str or None
- :param attrs: Hook attributes
- :type attrs: Dictionary
- :param server: Pointer to server
- """
- dflt_attributes = {}
- def __init__(self, name=None, attrs={}, server=None):
- self.logger = logging.getLogger(__name__)
- PBSObject.__init__(self, name, attrs, self.dflt_attributes)
- self.server = server
- class ResourceResv(PBSObject):
- """
- Generic PBS resource reservation, i.e., job or
- ``advance/standing`` reservation
- """
- def execvnode(self, attr='exec_vnode'):
- """
- PBS type execution vnode
- """
- if attr in self.attributes:
- return PbsTypeExecVnode(self.attributes[attr])
- else:
- return None
- def exechost(self):
- """
- PBS type execution host
- """
- if 'exec_host' in self.attributes:
- return PbsTypeExecHost(self.attributes['exec_host'])
- else:
- return None
- def resvnodes(self):
- """
- nodes assigned to a reservation
- """
- if 'resv_nodes' in self.attributes:
- return self.attributes['resv_nodes']
- else:
- return None
- def select(self):
- if hasattr(self, '_select') and self._select is not None:
- return self._select
- if 'schedselect' in self.attributes:
- self._select = PbsTypeSelect(self.attributes['schedselect'])
- elif 'select' in self.attributes:
- self._select = PbsTypeSelect(self.attributes['select'])
- else:
- return None
- return self._select
- @classmethod
- def get_hosts(cls, exechost=None):
- """
- :returns: The hosts portion of the exec_host
- """
- hosts = []
- exechosts = cls.utils.parse_exechost(exechost)
- if exechosts:
- for h in exechosts:
- eh = h.keys()[0]
- if eh not in hosts:
- hosts.append(eh)
- return hosts
- def get_vnodes(self, execvnode=None):
- """
- :returns: The unique vnode names of an execvnode as a list
- """
- if execvnode is None:
- if 'exec_vnode' in self.attributes:
- execvnode = self.attributes['exec_vnode']
- elif 'resv_nodes' in self.attributes:
- execvnode = self.attributes['resv_nodes']
- else:
- return []
- vnodes = []
- execvnodes = PbsTypeExecVnode(execvnode)
- if execvnodes:
- for n in execvnodes:
- ev = n.keys()[0]
- if ev not in vnodes:
- vnodes.append(ev)
- return vnodes
- def walltime(self, attr='Resource_List.walltime'):
- if attr in self.attributes:
- return self.utils.convert_duration(self.attributes[attr])
- class Job(ResourceResv):
- """
- PBS Job. Attributes and Resources
- :param username: Job username
- :type username: str or None
- :param attrs: Job attributes
- :type attrs: Dictionary
- :param jobname: Name of the PBS job
- :type jobname: str or None
- """
- dflt_attributes = {
- ATTR_N: 'STDIN',
- ATTR_j: 'n',
- ATTR_m: 'a',
- ATTR_p: '0',
- ATTR_r: 'y',
- ATTR_k: 'oe',
- }
- runtime = 100
- logger = logging.getLogger(__name__)
- du = DshUtils()
- def __init__(self, username=None, attrs={}, jobname=None):
- self.platform = self.du.get_platform()
- self.server = {}
- self.script = None
- self.script_body = None
- if username is not None:
- self.username = str(username)
- else:
- self.username = None
- self.du = None
- self.interactive_handle = None
- if self.platform == 'cray' or self.platform == 'craysim':
- if 'Resource_List.select' in attrs:
- select = attrs['Resource_List.select']
- attrs['Resource_List.select'] = self.add_cray_vntype(select)
- elif 'Resource_List.vntype' not in attrs:
- attrs['Resource_List.vntype'] = 'cray_compute'
- PBSObject.__init__(self, None, attrs, self.dflt_attributes)
- if jobname is not None:
- self.custom_attrs[ATTR_N] = jobname
- self.attributes[ATTR_N] = jobname
- self.set_variable_list(self.username)
- self.set_sleep_time(100)
- def add_cray_vntype(self, select=None):
- """
- Cray specific function to add vntype as ``cray_compute`` to each
- select chunk
- :param select: PBS select statement
- :type select: str or None
- """
- ra = []
- r = select.split('+')
- for i in r:
- select = PbsTypeSelect(i)
- novntype = 'vntype' not in select.resources
- nohost = 'host' not in select.resources
- novnode = 'vnode' not in select.resources
- if novntype and nohost and novnode:
- i = i + ":vntype=cray_compute"
- ra.append(i)
- select_str = ''
- for l in ra:
- select_str = select_str + "+" + l
- select_str = select_str[1:]
- return select_str
- def set_attributes(self, a={}):
- """
- set attributes and custom attributes on this job.
- custom attributes are used when converting attributes to CLI.
- In case of Cray platform if 'Resource_List.vntype' is set
- already then remove it and add vntype value to each chunk of a
- select statement.
- :param a: Attribute dictionary
- :type a: Dictionary
- """
- if isinstance(a, list):
- a = OrderedDict(a)
- self.attributes = OrderedDict(self.dflt_attributes.items() +
- self.attributes.items() + a.items())
- if self.platform == 'cray' or self.platform == 'craysim':
- s = 'Resource_List.select' in a
- v = 'Resource_List.vntype' in self.custom_attrs
- if s and v:
- del self.custom_attrs['Resource_List.vntype']
- select = a['Resource_List.select']
- a['Resource_List.select'] = self.add_cray_vntype(select)
- self.custom_attrs = OrderedDict(self.custom_attrs.items() +
- a.items())
- def set_variable_list(self, user=None, workdir=None):
- """
- Customize the ``Variable_List`` job attribute to ``<user>``
- """
- if user is None:
- userinfo = pwd.getpwuid(os.getuid())
- user = userinfo[0]
- homedir = userinfo[5]
- else:
- try:
- homedir = pwd.getpwnam(user)[5]
- except:
- homedir = ""
- self.username = user
- s = ['PBS_O_HOME=' + homedir]
- s += ['PBS_O_LANG=en_US.UTF-8']
- s += ['PBS_O_LOGNAME=' + user]
- s += ['PBS_O_PATH=/usr/bin:/bin:/usr/bin:/usr/local/bin']
- s += ['PBS_O_MAIL=/var/spool/mail/' + user]
- s += ['PBS_O_SHELL=/bin/bash']
- s += ['PBS_O_SYSTEM=Linux']
- if workdir is not None:
- wd = workdir
- else:
- wd = os.getcwd()
- s += ['PBS_O_WORKDIR=' + str(wd)]
- self.attributes[ATTR_v] = ",".join(s)
- self.set_attributes()
- def set_sleep_time(self, duration):
- """
- Set the sleep duration for this job.
- :param duration: The duration, in seconds, to sleep
- :type duration: int
- """
- self.set_execargs('/bin/sleep', duration)
- def set_execargs(self, executable, arguments=None):
- """
- Set the executable and arguments to use for this job
- :param executable: path to an executable. No checks are made.
- :type executable: str
- :param arguments: arguments to executable.
- :type arguments: str or list or int
- """
- msg = ['job: executable set to ' + str(executable)]
- if arguments is not None:
- msg += [' with arguments: ' + str(arguments)]
- self.logger.info("".join(msg))
- self.attributes[ATTR_executable] = executable
- if arguments is not None:
- args = ''
- xml_beginargs = '<jsdl-hpcpa:Argument>'
- xml_endargs = '</jsdl-hpcpa:Argument>'
- if isinstance(arguments, list):
- for a in arguments:
- args += xml_beginargs + str(a) + xml_endargs
- elif isinstance(arguments, str):
- args = xml_beginargs + arguments + xml_endargs
- elif isinstance(arguments, int):
- args = xml_beginargs + str(arguments) + xml_endargs
- self.attributes[ATTR_Arglist] = args
- else:
- self.unset_attributes([ATTR_Arglist])
- self.set_attributes()
- def create_script(self, body=None, asuser=None, hostname=None):
- """
- Create a job script from a given body of text into a
- temporary location
- :param body: the body of the script
- :type body: str or None
- :param asuser: Optionally the user to own this script,
- defaults ot current user
- :type asuser: str or None
- :param hostname: The host on which the job script is to
- be created
- :type hostname: str or None
- """
- if body is None:
- return None
- if isinstance(body, list):
- body = '\n'.join(body)
- if self.platform == 'cray' or self.platform == 'craysim':
- body = body.split("\n")
- for i, line in enumerate(body):
- if line.startswith("#PBS") and "select=" in line:
- if 'Resource_List.vntype' in self.attributes:
- self.unset_attributes(['Resource_List.vntype'])
- line_arr = line.split(" ")
- for j, element in enumerate(line_arr):
- select = element.startswith("select=")
- lselect = element.startswith("-lselect=")
- if select or lselect:
- if lselect:
- sel_str = element[9:]
- else:
- sel_str = element[7:]
- sel_str = self.add_cray_vntype(select=sel_str)
- if lselect:
- line_arr[j] = "-lselect=" + sel_str
- else:
- line_arr[j] = "select=" + sel_str
- body[i] = " ".join(line_arr)
- body = '\n'.join(body)
- self.script_body = body
- if self.du is None:
- self.du = DshUtils()
- # First create the temporary file as current user and only change
- # its mode once the current user has written to it
- fn = self.du.create_temp_file(hostname, prefix='PtlPbsJobScript',
- asuser=asuser, body=body)
- self.du.chmod(hostname, fn, mode=0755)
- if not self.du.is_localhost(hostname):
- self.du.run_copy(hostname, fn, fn)
- self.script = fn
- return fn
- def create_subjob_id(self, job_array_id, subjob_index):
- """
- insert subjob index into the square brackets of job array id
- :param job_array_id: PBS parent array job id
- :type job_array_id: str
- :param subjob_index: index of subjob
- :type subjob_index: int
- :returns: subjob id string
- """
- idx = job_array_id.find('[]')
- return job_array_id[:idx + 1] + str(subjob_index) + \
- job_array_id[idx + 1:]
- class Reservation(ResourceResv):
- """
- PBS Reservation. Attributes and Resources
- :param attrs: Reservation attributes
- :type attrs: Dictionary
- """
- dflt_attributes = {}
- def __init__(self, username=None, attrs={}):
- self.server = {}
- self.script = None
- self.attributes = attrs
- if username is None:
- userinfo = pwd.getpwuid(os.getuid())
- self.username = userinfo[0]
- else:
- self.username = str(username)
- # These are not in dflt_attributes because of the conversion to CLI
- # options is done strictly
- if ATTR_resv_start not in attrs:
- attrs[ATTR_resv_start] = str(int(time.time()) + 36 * 3600)
- if ATTR_resv_end not in attrs:
- if ATTR_resv_duration not in attrs:
- attrs[ATTR_resv_end] = str(int(time.time()) + 72 * 3600)
- PBSObject.__init__(self, None, attrs, self.dflt_attributes)
- self.set_attributes()
- def set_variable_list(self, user, workdir=None):
- pass
- class InteractiveJob(threading.Thread):
- """
- An Interactive Job thread
- Interactive Jobs are submitted as a thread that sets the jobid
- as soon as it is returned by ``qsub -I``, such that the caller
- can get back to monitoring the state of PBS while the interactive
- session goes on in the thread.
- The commands to be run within an interactive session are
- specified in the job's interactive_script attribute as a list of
- tuples, where the first item in each tuple is the command to run,
- and the subsequent items are the expected returned data.
- Implementation details:
- Support for interactive jobs is currently done through the
- pexpect module which must be installed separately from PTL.
- Interactive jobs are submitted through ``CLI`` only, there is no
- API support for this operation yet.
- The submission of an interactive job requires passing in job
- attributes,the command to execute ``(i.e. path to qsub -I)``
- and the hostname
- when not impersonating:
- pexpect spawns the ``qsub -I`` command and expects a prompt
- back, for each tuple in the interactive_script, it sends the
- command and expects to match the return value.
- when impersonating:
- pexpect spawns ``sudo -u <user> qsub -I``. The rest is as
- described in non- impersonating mode.
- """
- logger = logging.getLogger(__name__)
- pexpect_timeout = 15
- pexpect_sleep_time = .1
- du = DshUtils()
- def __init__(self, job, cmd, host):
- threading.Thread.__init__(self)
- self.job = job
- self.cmd = cmd
- self.jobid = None
- self.hostname = host
- def run(self):
- """
- Run the interactive job
- """
- try:
- import pexpect
- except:
- self.logger.error('pexpect module is required for '
- 'interactive jobs')
- return None
- job = self.job
- cmd = self.cmd
- self.jobid = None
- self.logger.info("submit interactive job as " + job.username +
- ": " + " ".join(cmd))
- if not hasattr(job, 'interactive_script'):
- self.logger.debug('no interactive_script attribute on job')
- return None
- try:
- # sleep to allow server to communicate with client
- # this value is set empirically so tweaking may be
- # needed
- _st = self.pexpect_sleep_time
- _to = self.pexpect_timeout
- _sc = job.interactive_script
- current_user = pwd.getpwuid(os.getuid())[0]
- if current_user != job.username:
- if hasattr(job, 'preserve_env') and job.preserve_env is True:
- cmd = ['sudo', '-E', '-u', job.username] + cmd
- else:
- cmd = ['sudo', '-u', job.username] + cmd
- self.logger.debug(cmd)
- _p = pexpect.spawn(" ".join(cmd), timeout=_to)
- self.job.interactive_handle = _p
- time.sleep(_st)
- expstr = "qsub: waiting for job "
- expstr += "(?P<jobid>\d+.[0-9A-Za-z-.]+) to start"
- _p.expect(expstr)
- if _p.match:
- self.jobid = _p.match.group('jobid')
- else:
- _p.close()
- self.job.interactive_handle = None
- return None
- self.logger.debug(_p.after.decode())
- for _l in _sc:
- (cmd, out) = _l
- self.logger.info('sending: ' + cmd)
- _p.sendline(cmd)
- self.logger.info('expecting: ' + out)
- _p.expect(out)
- self.logger.info('sending exit')
- _p.sendline("exit")
- self.logger.info('waiting for the subprocess to finish')
- _p.wait()
- _p.close()
- self.job.interactive_handle = None
- self.logger.debug(_p.exitstatus)
- except Exception:
- self.logger.error(traceback.print_exc())
- return None
- return self.jobid
- class Queue(PBSObject):
- """
- PBS Queue container, holds attributes of the queue and
- pointer to server
- :param name: Queue name
- :type name: str or None
- :param attrs: Queue attributes
- :type attrs: Dictionary
- """
- dflt_attributes = {}
- def __init__(self, name=None, attrs={}, server=None):
- self.logger = logging.getLogger(__name__)
- PBSObject.__init__(self, name, attrs, self.dflt_attributes)
- self.server = server
- m = ['queue']
- if server is not None:
- m += ['@' + server.shortname]
- if self.name is not None:
- m += [' ', self.name]
- m += [': ']
- self.logprefix = "".join(m)
- def revert_to_defaults(self):
- """
- reset queue attributes to defaults
- """
- ignore_attrs = ['id', ATTR_count, ATTR_rescassn]
- ignore_attrs += [ATTR_qtype, ATTR_enable, ATTR_start, ATTR_total]
- ignore_attrs += ['THE_END']
- len_attrs = len(ignore_attrs)
- unsetlist = []
- setdict = {}
- self.logger.info(
- self.logprefix +
- "reverting configuration to defaults")
- if self.server is not None:
- self.server.status(QUEUE, id=self.name, level=logging.DEBUG)
- for k in self.attributes.keys():
- for i in range(len_attrs):
- if k.startswith(ignore_attrs[i]):
- break
- if (i == (len_attrs - 1)) and k not in self.dflt_attributes:
- unsetlist.append(k)
- if len(unsetlist) != 0 and self.server is not None:
- try:
- self.server.manager(MGR_CMD_UNSET, MGR_OBJ_QUEUE, unsetlist,
- self.name)
- except PbsManagerError, e:
- self.logger.error(e.msg)
- for k in self.dflt_attributes.keys():
- if (k not in self.attributes or
- self.attributes[k] != self.dflt_attributes[k]):
- setdict[k] = self.dflt_attributes[k]
- if len(setdict.keys()) != 0 and self.server is not None:
- self.server.manager(MGR_CMD_SET, MGR_OBJ_QUEUE, setdict)
- class PBSInitServices(object):
- """
- PBS initialization services
- :param hostname: Machine hostname
- :type hostname: str or None
- :param conf: PBS configuaration file
- :type conf: str or None
- """
- def __init__(self, hostname=None, conf=None):
- self.logger = logging.getLogger(__name__)
- self.hostname = hostname
- if self.hostname is None:
- self.hostname = socket.gethostname()
- self.dflt_conf_file = os.environ.get('PBS_CONF_FILE', '/etc/pbs.conf')
- self.conf_file = conf
- self.du = DshUtils()
- self.is_linux = sys.platform.startswith('linux')
- def initd(self, hostname=None, op='status', conf_file=None,
- init_script=None, daemon='all'):
- """
- Run the init script for a given operation
- :param hostname: hostname on which to execute the init script
- :type hostname: str or None
- :param op: one of status, start, stop, restart
- :type op: str
- :param conf_file: optional path to a configuration file
- :type conf_file: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- :param daemon: name of daemon to operate on. one of server, mom,
- sched, comm or all
- :type daemon: str
- """
- if hostname is None:
- hostname = self.hostname
- if conf_file is None:
- conf_file = self.conf_file
- return self._unix_initd(hostname, op, conf_file, init_script, daemon)
- def restart(self, hostname=None, init_script=None):
- """
- Run the init script for a restart operation
- :param hostname: hostname on which to execute the init script
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='restart', init_script=init_script)
- def restart_server(self, hostname=None, init_script=None):
- """
- Run the init script for a restart server
- :param hostname: hostname on which to restart server
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='restart', init_script=init_script,
- daemon='server')
- def restart_mom(self, hostname=None, init_script=None):
- """
- Run the init script for a restart mom
- :param hostname: hostname on which to restart mom
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='restart', init_script=init_script,
- daemon='mom')
- def restart_sched(self, hostname=None, init_script=None):
- """
- Run the init script for a restart sched
- :param hostname: hostname on which to restart sched
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='restart', init_script=init_script,
- daemon='sched')
- def restart_comm(self, hostname=None, init_script=None):
- """
- Run the init script for a restart comm
- :param hostname: hostname on which to restart comm
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='restart', init_script=init_script,
- daemon='comm')
- def start(self, hostname=None, init_script=None):
- """
- Run the init script for a start operation
- :param hostname: hostname on which to execute the init script
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='start', init_script=init_script)
- def start_server(self, hostname=None, init_script=None):
- """
- Run the init script for a start server
- :param hostname: hostname on which to start server
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='start', init_script=init_script,
- daemon='server')
- def start_mom(self, hostname=None, init_script=None):
- """
- Run the init script for a start mom
- :param hostname: hostname on which to start mom
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='start', init_script=init_script,
- daemon='mom')
- def start_sched(self, hostname=None, init_script=None):
- """
- Run the init script for a start sched
- :param hostname: hostname on which to start sched
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='start', init_script=init_script,
- daemon='sched')
- def start_comm(self, hostname=None, init_script=None):
- """
- Run the init script for a start comm
- :param hostname: hostname on which to start comm
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='start', init_script=init_script,
- daemon='comm')
- def stop(self, hostname=None, init_script=None):
- """
- Run the init script for a stop operation
- :param hostname: hostname on which to execute the init script
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='stop', init_script=init_script)
- def stop_server(self, hostname=None, init_script=None):
- """
- Run the init script for a stop server
- :param hostname: hostname on which to stop server
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='stop', init_script=init_script,
- daemon='server')
- def stop_mom(self, hostname=None, init_script=None):
- """
- Run the init script for a stop mom
- :param hostname: hostname on which to stop mom
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='stop', init_script=init_script,
- daemon='mom')
- def stop_sched(self, hostname=None, init_script=None):
- """
- Run the init script for a stop sched
- :param hostname: hostname on which to stop sched
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='stop', init_script=init_script,
- daemon='sched')
- def stop_comm(self, hostname=None, init_script=None):
- """
- Run the init script for a stop comm
- :param hostname: hostname on which to stop comm
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='stop', init_script=init_script,
- daemon='comm')
- def status(self, hostname=None, init_script=None):
- """
- Run the init script for a status operation
- :param hostname: hostname on which to execute the init script
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='status', init_script=init_script)
- def status_server(self, hostname=None, init_script=None):
- """
- Run the init script for a status server
- :param hostname: hostname on which to status server
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='status', init_script=init_script,
- daemon='server')
- def status_mom(self, hostname=None, init_script=None):
- """
- Run the init script for a status mom
- :param hostname: hostname on which to status mom
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='status', init_script=init_script,
- daemon='mom')
- def status_sched(self, hostname=None, init_script=None):
- """
- Run the init script for a status sched
- :param hostname: hostname on which to status sched
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='status', init_script=init_script,
- daemon='sched')
- def status_comm(self, hostname=None, init_script=None):
- """
- Run the init script for a status comm
- :param hostname: hostname on which to status comm
- :type hostname: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- """
- return self.initd(hostname, op='status', init_script=init_script,
- daemon='comm')
- def _unix_initd(self, hostname, op, conf_file, init_script, daemon):
- """
- Helper function for initd ``(*nix version)``
- :param hostname: hostname on which init script should run
- :type hostname: str
- :param op: Operation on daemons - start, stop, restart or status
- :op type: str
- :param conf_file: Optional path to the pbs configuration file
- :type conf_file: str or None
- :param init_script: optional path to a PBS init script
- :type init_script: str or None
- :param daemon: name of daemon to operate on. one of server, mom,
- sched, comm or all
- :type daemon: str
- """
- init_cmd = ['sudo']
- if daemon is not None and daemon != 'all':
- conf = self.du.parse_pbs_config(hostname, conf_file)
- dconf = {
- 'PBS_START_SERVER': 0,
- 'PBS_START_MOM': 0,
- 'PBS_START_SCHED': 0,
- 'PBS_START_COMM': 0
- }
- if daemon == 'server' and conf.get('PBS_START_SERVER', 0) != 0:
- dconf['PBS_START_SERVER'] = 1
- elif daemon == 'mom' and conf.get('PBS_START_MOM', 0) != 0:
- dconf['PBS_START_MOM'] = 1
- elif daemon == 'sched' and conf.get('PBS_START_SCHED', 0) != 0:
- dconf['PBS_START_SCHED'] = 1
- elif daemon == 'comm' and conf.get('PBS_START_COMM', 0) != 0:
- dconf['PBS_START_COMM'] = 1
- for k, v in dconf.items():
- init_cmd += ["%s=%s" % (k, str(v))]
- _as = True
- else:
- fn = None
- if (conf_file is not None) and (conf_file != self.dflt_conf_file):
- init_cmd += ['PBS_CONF_FILE=' + conf_file]
- _as = True
- else:
- _as = False
- conf = self.du.parse_pbs_config(hostname, conf_file)
- if (init_script is None) or (not init_script.startswith('/')):
- if 'PBS_EXEC' not in conf:
- msg = 'Missing PBS_EXEC setting in pbs config'
- raise PbsInitServicesError(rc=1, rv=False, msg=msg)
- if init_script is None:
- init_script = os.path.join(conf['PBS_EXEC'], 'libexec',
- 'pbs_init.d')
- else:
- init_script = os.path.join(conf['PBS_EXEC'], 'etc',
- init_script)
- if not self.du.isfile(hostname, path=init_script, sudo=True):
- # Could be Type 3 installation where we will not have
- # PBS_EXEC/libexec/pbs_init.d
- return []
- init_cmd += [init_script, op]
- msg = 'running init script to ' + op + ' pbs'
- if daemon is not None and daemon != 'all':
- msg += ' ' + daemon
- msg += ' on ' + hostname
- if conf_file is not None:
- msg += ' using ' + conf_file
- msg += ' init_cmd=%s' % (str(init_cmd))
- self.logger.info(msg)
- ret = self.du.run_cmd(hostname, init_cmd, as_script=_as,
- logerr=False)
- if ret['rc'] != 0:
- raise PbsInitServicesError(rc=ret['rc'], rv=False,
- msg='\n'.join(ret['err']))
- else:
- return ret
- def switch_version(self, hostname=None, version=None):
- """
- Switch to another version of PBS installed on the system
- :param hostname: The hostname to operate on
- :type hostname: str or None
- :param version: version to switch
- """
- pbs_conf = self.du.parse_pbs_config(hostname)
- if 'PBS_EXEC' in pbs_conf:
- dn = os.path.dirname(pbs_conf['PBS_EXEC'])
- newver = os.path.join(dn, version)
- ret = self.du.isdir(hostname, path=newver)
- if not ret:
- msg = 'no version ' + version + ' on host ' + hostname
- raise PbsInitServicesError(rc=0, rv=False, msg=msg)
- self.stop(hostname)
- dflt = os.path.join(dn, 'default')
- ret = self.du.isfile(hostname, path=dflt)
- if ret:
- self.logger.info('removing symbolic link ' + dflt)
- self.du.rm(hostname, dflt, sudo=True, logerr=False)
- self.du.set_pbs_config(hostname, confs={'PBS_EXEC': dflt})
- else:
- self.du.set_pbs_config(hostname, confs={'PBS_EXEC': newver})
- self.logger.info('linking ' + newver + ' to ' + dflt)
- self.du.run_cmd(hostname, ['ln', '-s', newver, dflt],
- sudo=True, logerr=False)
- self.start(hostname)
|