12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package validation
- import (
- "bytes"
- "fmt"
- "math"
- "reflect"
- "strings"
- "testing"
- "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/intstr"
- "k8s.io/apimachinery/pkg/util/validation"
- "k8s.io/apimachinery/pkg/util/validation/field"
- utilfeature "k8s.io/apiserver/pkg/util/feature"
- featuregatetesting "k8s.io/component-base/featuregate/testing"
- "k8s.io/kubernetes/pkg/apis/core"
- "k8s.io/kubernetes/pkg/capabilities"
- "k8s.io/kubernetes/pkg/features"
- "k8s.io/kubernetes/pkg/security/apparmor"
- utilpointer "k8s.io/utils/pointer"
- )
- const (
- dnsLabelErrMsg = "a DNS-1123 label must consist of"
- dnsSubdomainLabelErrMsg = "a DNS-1123 subdomain"
- envVarNameErrMsg = "a valid environment variable name must consist of"
- )
- func newHostPathType(pathType string) *core.HostPathType {
- hostPathType := new(core.HostPathType)
- *hostPathType = core.HostPathType(pathType)
- return hostPathType
- }
- func testVolume(name string, namespace string, spec core.PersistentVolumeSpec) *core.PersistentVolume {
- objMeta := metav1.ObjectMeta{Name: name}
- if namespace != "" {
- objMeta.Namespace = namespace
- }
- return &core.PersistentVolume{
- ObjectMeta: objMeta,
- Spec: spec,
- }
- }
- func TestValidatePersistentVolumes(t *testing.T) {
- validMode := core.PersistentVolumeFilesystem
- invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
- scenarios := map[string]struct {
- isExpectedFailure bool
- volume *core.PersistentVolume
- }{
- "good-volume": {
- isExpectedFailure: false,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "good-volume-with-capacity-unit": {
- isExpectedFailure: false,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10Gi"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "good-volume-without-capacity-unit": {
- isExpectedFailure: false,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "good-volume-with-storage-class": {
- isExpectedFailure: false,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- StorageClassName: "valid",
- }),
- },
- "good-volume-with-retain-policy": {
- isExpectedFailure: false,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRetain,
- }),
- },
- "good-volume-with-volume-mode": {
- isExpectedFailure: false,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- VolumeMode: &validMode,
- }),
- },
- "invalid-accessmode": {
- isExpectedFailure: true,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "invalid-reclaimpolicy": {
- isExpectedFailure: true,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- PersistentVolumeReclaimPolicy: "fakeReclaimPolicy",
- }),
- },
- "invalid-volume-mode": {
- isExpectedFailure: true,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- VolumeMode: &invalidMode,
- }),
- },
- "unexpected-namespace": {
- isExpectedFailure: true,
- volume: testVolume("foo", "unexpected-namespace", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "missing-volume-source": {
- isExpectedFailure: true,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- }),
- },
- "bad-name": {
- isExpectedFailure: true,
- volume: testVolume("123*Bad(Name", "unexpected-namespace", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "missing-name": {
- isExpectedFailure: true,
- volume: testVolume("", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "missing-capacity": {
- isExpectedFailure: true,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "bad-volume-zero-capacity": {
- isExpectedFailure: true,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("0"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "missing-accessmodes": {
- isExpectedFailure: true,
- volume: testVolume("goodname", "missing-accessmodes", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- }),
- },
- "too-many-sources": {
- isExpectedFailure: true,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("5G"),
- },
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"},
- },
- }),
- },
- "host mount of / with recycle reclaim policy": {
- isExpectedFailure: true,
- volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
- }),
- },
- "host mount of / with recycle reclaim policy 2": {
- isExpectedFailure: true,
- volume: testVolume("bad-recycle-do-not-want", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/a/..",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
- }),
- },
- "invalid-storage-class-name": {
- isExpectedFailure: true,
- volume: testVolume("invalid-storage-class-name", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- StorageClassName: "-invalid-",
- }),
- },
- "bad-hostpath-volume-backsteps": {
- isExpectedFailure: true,
- volume: testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo/..",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- StorageClassName: "backstep-hostpath",
- }),
- },
- "volume-node-affinity": {
- isExpectedFailure: false,
- volume: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
- },
- "volume-empty-node-affinity": {
- isExpectedFailure: true,
- volume: testVolumeWithNodeAffinity(&core.VolumeNodeAffinity{}),
- },
- "volume-bad-node-affinity": {
- isExpectedFailure: true,
- volume: testVolumeWithNodeAffinity(
- &core.VolumeNodeAffinity{
- Required: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchExpressions: []core.NodeSelectorRequirement{
- {
- Operator: core.NodeSelectorOpIn,
- Values: []string{"test-label-value"},
- },
- },
- },
- },
- },
- }),
- },
- }
- for name, scenario := range scenarios {
- t.Run(name, func(t *testing.T) {
- errs := ValidatePersistentVolume(scenario.volume)
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- })
- }
- }
- func TestValidatePersistentVolumeSpec(t *testing.T) {
- fsmode := core.PersistentVolumeFilesystem
- blockmode := core.PersistentVolumeBlock
- scenarios := map[string]struct {
- isExpectedFailure bool
- isInlineSpec bool
- pvSpec *core.PersistentVolumeSpec
- }{
- "pv-pvspec-valid": {
- isExpectedFailure: false,
- isInlineSpec: false,
- pvSpec: &core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- StorageClassName: "testclass",
- PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- VolumeMode: &fsmode,
- NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"),
- },
- },
- "inline-pvspec-with-capacity": {
- isExpectedFailure: true,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- },
- },
- "inline-pvspec-with-sc": {
- isExpectedFailure: true,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- StorageClassName: "testclass",
- },
- },
- "inline-pvspec-with-non-fs-volume-mode": {
- isExpectedFailure: true,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- VolumeMode: &blockmode,
- },
- },
- "inline-pvspec-with-non-retain-reclaim-policy": {
- isExpectedFailure: true,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimRecycle,
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- },
- },
- "inline-pvspec-with-node-affinity": {
- isExpectedFailure: true,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- NodeAffinity: simpleVolumeNodeAffinity("foo", "bar"),
- },
- },
- "inline-pvspec-with-non-csi-source": {
- isExpectedFailure: true,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- },
- },
- "inline-pvspec-valid-with-access-modes-and-mount-options": {
- isExpectedFailure: false,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- MountOptions: []string{"soft", "read-write"},
- },
- },
- "inline-pvspec-valid-with-access-modes": {
- isExpectedFailure: false,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- },
- },
- "inline-pvspec-with-missing-acess-modes": {
- isExpectedFailure: true,
- isInlineSpec: true,
- pvSpec: &core.PersistentVolumeSpec{
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- MountOptions: []string{"soft", "read-write"},
- },
- },
- }
- for name, scenario := range scenarios {
- errs := ValidatePersistentVolumeSpec(scenario.pvSpec, "", scenario.isInlineSpec, field.NewPath("field"))
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- }
- }
- func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
- validVolume := testVolume("foo", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- StorageClassName: "valid",
- })
- validPvSourceNoUpdate := validVolume.DeepCopy()
- invalidPvSourceUpdateType := validVolume.DeepCopy()
- invalidPvSourceUpdateType.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
- FlexVolume: &core.FlexPersistentVolumeSource{
- Driver: "kubernetes.io/blue",
- FSType: "ext4",
- },
- }
- invalidPvSourceUpdateDeep := validVolume.DeepCopy()
- invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/updated",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- }
- validCSIVolume := testVolume("csi-volume", "", core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- CSI: &core.CSIPersistentVolumeSource{
- Driver: "come.google.gcepd",
- VolumeHandle: "foobar",
- },
- },
- StorageClassName: "gp2",
- })
- expandSecretRef := &core.SecretReference{
- Name: "expansion-secret",
- Namespace: "default",
- }
- scenarios := map[string]struct {
- isExpectedFailure bool
- csiExpansionEnabled bool
- oldVolume *core.PersistentVolume
- newVolume *core.PersistentVolume
- }{
- "condition-no-update": {
- isExpectedFailure: false,
- oldVolume: validVolume,
- newVolume: validPvSourceNoUpdate,
- },
- "condition-update-source-type": {
- isExpectedFailure: true,
- oldVolume: validVolume,
- newVolume: invalidPvSourceUpdateType,
- },
- "condition-update-source-deep": {
- isExpectedFailure: true,
- oldVolume: validVolume,
- newVolume: invalidPvSourceUpdateDeep,
- },
- "csi-expansion-enabled-with-pv-secret": {
- csiExpansionEnabled: true,
- isExpectedFailure: false,
- oldVolume: validCSIVolume,
- newVolume: getCSIVolumeWithSecret(validCSIVolume, expandSecretRef),
- },
- "csi-expansion-enabled-with-old-pv-secret": {
- csiExpansionEnabled: true,
- isExpectedFailure: true,
- oldVolume: getCSIVolumeWithSecret(validCSIVolume, expandSecretRef),
- newVolume: getCSIVolumeWithSecret(validCSIVolume, &core.SecretReference{
- Name: "foo-secret",
- Namespace: "default",
- }),
- },
- }
- for name, scenario := range scenarios {
- errs := ValidatePersistentVolumeUpdate(scenario.newVolume, scenario.oldVolume)
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- }
- }
- func getCSIVolumeWithSecret(pv *core.PersistentVolume, secret *core.SecretReference) *core.PersistentVolume {
- pvCopy := pv.DeepCopy()
- if secret != nil {
- pvCopy.Spec.CSI.ControllerExpandSecretRef = secret
- }
- return pvCopy
- }
- func testLocalVolume(path string, affinity *core.VolumeNodeAffinity) core.PersistentVolumeSpec {
- return core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- Local: &core.LocalVolumeSource{
- Path: path,
- },
- },
- NodeAffinity: affinity,
- StorageClassName: "test-storage-class",
- }
- }
- func TestValidateLocalVolumes(t *testing.T) {
- scenarios := map[string]struct {
- isExpectedFailure bool
- volume *core.PersistentVolume
- }{
- "alpha invalid local volume nil annotations": {
- isExpectedFailure: true,
- volume: testVolume(
- "invalid-local-volume-nil-annotations",
- "",
- testLocalVolume("/foo", nil)),
- },
- "valid local volume": {
- isExpectedFailure: false,
- volume: testVolume("valid-local-volume", "",
- testLocalVolume("/foo", simpleVolumeNodeAffinity("foo", "bar"))),
- },
- "invalid local volume no node affinity": {
- isExpectedFailure: true,
- volume: testVolume("invalid-local-volume-no-node-affinity", "",
- testLocalVolume("/foo", nil)),
- },
- "invalid local volume empty path": {
- isExpectedFailure: true,
- volume: testVolume("invalid-local-volume-empty-path", "",
- testLocalVolume("", simpleVolumeNodeAffinity("foo", "bar"))),
- },
- "invalid-local-volume-backsteps": {
- isExpectedFailure: true,
- volume: testVolume("foo", "",
- testLocalVolume("/foo/..", simpleVolumeNodeAffinity("foo", "bar"))),
- },
- "valid-local-volume-relative-path": {
- isExpectedFailure: false,
- volume: testVolume("foo", "",
- testLocalVolume("foo", simpleVolumeNodeAffinity("foo", "bar"))),
- },
- }
- for name, scenario := range scenarios {
- errs := ValidatePersistentVolume(scenario.volume)
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- }
- }
- func testVolumeWithNodeAffinity(affinity *core.VolumeNodeAffinity) *core.PersistentVolume {
- return testVolume("test-affinity-volume", "",
- core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
- PDName: "foo",
- },
- },
- StorageClassName: "test-storage-class",
- NodeAffinity: affinity,
- })
- }
- func simpleVolumeNodeAffinity(key, value string) *core.VolumeNodeAffinity {
- return &core.VolumeNodeAffinity{
- Required: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchExpressions: []core.NodeSelectorRequirement{
- {
- Key: key,
- Operator: core.NodeSelectorOpIn,
- Values: []string{value},
- },
- },
- },
- },
- },
- }
- }
- func TestValidateVolumeNodeAffinityUpdate(t *testing.T) {
- scenarios := map[string]struct {
- isExpectedFailure bool
- oldPV *core.PersistentVolume
- newPV *core.PersistentVolume
- }{
- "nil-nothing-changed": {
- isExpectedFailure: false,
- oldPV: testVolumeWithNodeAffinity(nil),
- newPV: testVolumeWithNodeAffinity(nil),
- },
- "affinity-nothing-changed": {
- isExpectedFailure: false,
- oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
- newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
- },
- "affinity-changed": {
- isExpectedFailure: true,
- oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
- newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar2")),
- },
- "nil-to-obj": {
- isExpectedFailure: false,
- oldPV: testVolumeWithNodeAffinity(nil),
- newPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
- },
- "obj-to-nil": {
- isExpectedFailure: true,
- oldPV: testVolumeWithNodeAffinity(simpleVolumeNodeAffinity("foo", "bar")),
- newPV: testVolumeWithNodeAffinity(nil),
- },
- }
- for name, scenario := range scenarios {
- errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV)
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- }
- }
- func testVolumeClaim(name string, namespace string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
- return &core.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
- Spec: spec,
- }
- }
- func testVolumeClaimWithStatus(
- name, namespace string,
- spec core.PersistentVolumeClaimSpec,
- status core.PersistentVolumeClaimStatus) *core.PersistentVolumeClaim {
- return &core.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
- Spec: spec,
- Status: status,
- }
- }
- func testVolumeClaimStorageClass(name string, namespace string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
- annotations := map[string]string{
- v1.BetaStorageClassAnnotation: annval,
- }
- return &core.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: namespace,
- Annotations: annotations,
- },
- Spec: spec,
- }
- }
- func testVolumeClaimAnnotation(name string, namespace string, ann string, annval string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
- annotations := map[string]string{
- ann: annval,
- }
- return &core.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: namespace,
- Annotations: annotations,
- },
- Spec: spec,
- }
- }
- func testVolumeClaimStorageClassInSpec(name, namespace, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
- spec.StorageClassName = &scName
- return &core.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: namespace,
- },
- Spec: spec,
- }
- }
- func testVolumeSnapshotDataSourceInSpec(name string, kind string, apiGroup string) *core.PersistentVolumeClaimSpec {
- scName := "csi-plugin"
- dataSourceInSpec := core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- StorageClassName: &scName,
- DataSource: &core.TypedLocalObjectReference{
- APIGroup: &apiGroup,
- Kind: kind,
- Name: name,
- },
- }
- return &dataSourceInSpec
- }
- func TestAlphaVolumeSnapshotDataSource(t *testing.T) {
- successTestCases := []core.PersistentVolumeClaimSpec{
- *testVolumeSnapshotDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
- }
- failedTestCases := []core.PersistentVolumeClaimSpec{
- *testVolumeSnapshotDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
- *testVolumeSnapshotDataSourceInSpec("test_snapshot", "PersistentVolumeClaim", "snapshot.storage.k8s.io"),
- *testVolumeSnapshotDataSourceInSpec("test_snapshot", "VolumeSnapshot", "storage.k8s.io"),
- }
- for _, tc := range successTestCases {
- if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- for _, tc := range failedTestCases {
- if errs := ValidatePersistentVolumeClaimSpec(&tc, field.NewPath("spec")); len(errs) == 0 {
- t.Errorf("expected failure: %v", errs)
- }
- }
- }
- func testVolumeClaimStorageClassInAnnotationAndSpec(name, namespace, scNameInAnn, scName string, spec core.PersistentVolumeClaimSpec) *core.PersistentVolumeClaim {
- spec.StorageClassName = &scName
- return &core.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: namespace,
- Annotations: map[string]string{v1.BetaStorageClassAnnotation: scNameInAnn},
- },
- Spec: spec,
- }
- }
- func TestValidatePersistentVolumeClaim(t *testing.T) {
- invalidClassName := "-invalid-"
- validClassName := "valid"
- invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
- validMode := core.PersistentVolumeFilesystem
- scenarios := map[string]struct {
- isExpectedFailure bool
- claim *core.PersistentVolumeClaim
- }{
- "good-claim": {
- isExpectedFailure: false,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- Selector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: "Exists",
- },
- },
- },
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- StorageClassName: &validClassName,
- VolumeMode: &validMode,
- }),
- },
- "invalid-claim-zero-capacity": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- Selector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: "Exists",
- },
- },
- },
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
- },
- },
- StorageClassName: &validClassName,
- }),
- },
- "invalid-label-selector": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- Selector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: "InvalidOp",
- Values: []string{"value1", "value2"},
- },
- },
- },
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- }),
- },
- "invalid-accessmode": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- }),
- },
- "missing-namespace": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- }),
- },
- "no-access-modes": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- }),
- },
- "no-resource-requests": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- },
- }),
- },
- "invalid-resource-requests": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- },
- },
- }),
- },
- "negative-storage-request": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- Selector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: "Exists",
- },
- },
- },
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("-10G"),
- },
- },
- }),
- },
- "zero-storage-request": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- Selector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: "Exists",
- },
- },
- },
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
- },
- },
- }),
- },
- "invalid-storage-class-name": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- Selector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: "Exists",
- },
- },
- },
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- StorageClassName: &invalidClassName,
- }),
- },
- "invalid-volume-mode": {
- isExpectedFailure: true,
- claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeMode: &invalidMode,
- }),
- },
- }
- for name, scenario := range scenarios {
- t.Run(name, func(t *testing.T) {
- errs := ValidatePersistentVolumeClaim(scenario.claim)
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- })
- }
- }
- func TestAlphaPVVolumeModeUpdate(t *testing.T) {
- block := core.PersistentVolumeBlock
- file := core.PersistentVolumeFilesystem
- scenarios := map[string]struct {
- isExpectedFailure bool
- oldPV *core.PersistentVolume
- newPV *core.PersistentVolume
- }{
- "valid-update-volume-mode-block-to-block": {
- isExpectedFailure: false,
- oldPV: createTestVolModePV(&block),
- newPV: createTestVolModePV(&block),
- },
- "valid-update-volume-mode-file-to-file": {
- isExpectedFailure: false,
- oldPV: createTestVolModePV(&file),
- newPV: createTestVolModePV(&file),
- },
- "invalid-update-volume-mode-to-block": {
- isExpectedFailure: true,
- oldPV: createTestVolModePV(&file),
- newPV: createTestVolModePV(&block),
- },
- "invalid-update-volume-mode-to-file": {
- isExpectedFailure: true,
- oldPV: createTestVolModePV(&block),
- newPV: createTestVolModePV(&file),
- },
- "invalid-update-volume-mode-nil-to-file": {
- isExpectedFailure: true,
- oldPV: createTestVolModePV(nil),
- newPV: createTestVolModePV(&file),
- },
- "invalid-update-volume-mode-nil-to-block": {
- isExpectedFailure: true,
- oldPV: createTestVolModePV(nil),
- newPV: createTestVolModePV(&block),
- },
- "invalid-update-volume-mode-file-to-nil": {
- isExpectedFailure: true,
- oldPV: createTestVolModePV(&file),
- newPV: createTestVolModePV(nil),
- },
- "invalid-update-volume-mode-block-to-nil": {
- isExpectedFailure: true,
- oldPV: createTestVolModePV(&block),
- newPV: createTestVolModePV(nil),
- },
- "invalid-update-volume-mode-nil-to-nil": {
- isExpectedFailure: false,
- oldPV: createTestVolModePV(nil),
- newPV: createTestVolModePV(nil),
- },
- "invalid-update-volume-mode-empty-to-mode": {
- isExpectedFailure: true,
- oldPV: createTestPV(),
- newPV: createTestVolModePV(&block),
- },
- }
- for name, scenario := range scenarios {
- t.Run(name, func(t *testing.T) {
- // ensure we have a resource version specified for updates
- errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV)
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- })
- }
- }
- func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
- block := core.PersistentVolumeBlock
- file := core.PersistentVolumeFilesystem
- validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- }, core.PersistentVolumeClaimStatus{
- Phase: core.ClaimBound,
- })
- validClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- })
- validClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "foo-description", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- })
- validUpdateClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeName: "volume",
- })
- invalidUpdateClaimResources := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("20G"),
- },
- },
- VolumeName: "volume",
- })
- invalidUpdateClaimAccessModes := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeName: "volume",
- })
- validClaimVolumeModeFile := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- },
- VolumeMode: &file,
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeName: "volume",
- })
- validClaimVolumeModeBlock := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- },
- VolumeMode: &block,
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeName: "volume",
- })
- invalidClaimVolumeModeNil := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- },
- VolumeMode: nil,
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeName: "volume",
- })
- invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeName: "volume",
- })
- validUpdateClaimMutableAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeName: "volume",
- })
- validAddClaimAnnotation := testVolumeClaimAnnotation("foo", "ns", "description", "updated-or-added-foo-description", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- VolumeName: "volume",
- })
- validSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("15G"),
- },
- },
- }, core.PersistentVolumeClaimStatus{
- Phase: core.ClaimBound,
- })
- invalidSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("5G"),
- },
- },
- }, core.PersistentVolumeClaimStatus{
- Phase: core.ClaimBound,
- })
- unboundSizeUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("12G"),
- },
- },
- }, core.PersistentVolumeClaimStatus{
- Phase: core.ClaimPending,
- })
- validClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- })
- invalidClaimStorageClassInSpec := testVolumeClaimStorageClassInSpec("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- })
- validClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec(
- "foo", "ns", "fast", "fast", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- })
- invalidClaimStorageClassInAnnotationAndSpec := testVolumeClaimStorageClassInAnnotationAndSpec(
- "foo", "ns", "fast2", "fast", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- })
- scenarios := map[string]struct {
- isExpectedFailure bool
- oldClaim *core.PersistentVolumeClaim
- newClaim *core.PersistentVolumeClaim
- enableResize bool
- enableBlock bool
- }{
- "valid-update-volumeName-only": {
- isExpectedFailure: false,
- oldClaim: validClaim,
- newClaim: validUpdateClaim,
- enableResize: false,
- enableBlock: false,
- },
- "valid-no-op-update": {
- isExpectedFailure: false,
- oldClaim: validUpdateClaim,
- newClaim: validUpdateClaim,
- enableResize: false,
- enableBlock: false,
- },
- "invalid-update-change-resources-on-bound-claim": {
- isExpectedFailure: true,
- oldClaim: validUpdateClaim,
- newClaim: invalidUpdateClaimResources,
- enableResize: false,
- enableBlock: false,
- },
- "invalid-update-change-access-modes-on-bound-claim": {
- isExpectedFailure: true,
- oldClaim: validUpdateClaim,
- newClaim: invalidUpdateClaimAccessModes,
- enableResize: false,
- enableBlock: false,
- },
- "valid-update-volume-mode-block-to-block": {
- isExpectedFailure: false,
- oldClaim: validClaimVolumeModeBlock,
- newClaim: validClaimVolumeModeBlock,
- enableResize: false,
- enableBlock: true,
- },
- "valid-update-volume-mode-file-to-file": {
- isExpectedFailure: false,
- oldClaim: validClaimVolumeModeFile,
- newClaim: validClaimVolumeModeFile,
- enableResize: false,
- enableBlock: true,
- },
- "invalid-update-volume-mode-to-block": {
- isExpectedFailure: true,
- oldClaim: validClaimVolumeModeFile,
- newClaim: validClaimVolumeModeBlock,
- enableResize: false,
- enableBlock: true,
- },
- "invalid-update-volume-mode-to-file": {
- isExpectedFailure: true,
- oldClaim: validClaimVolumeModeBlock,
- newClaim: validClaimVolumeModeFile,
- enableResize: false,
- enableBlock: true,
- },
- "invalid-update-volume-mode-nil-to-file": {
- isExpectedFailure: true,
- oldClaim: invalidClaimVolumeModeNil,
- newClaim: validClaimVolumeModeFile,
- enableResize: false,
- enableBlock: true,
- },
- "invalid-update-volume-mode-nil-to-block": {
- isExpectedFailure: true,
- oldClaim: invalidClaimVolumeModeNil,
- newClaim: validClaimVolumeModeBlock,
- enableResize: false,
- enableBlock: true,
- },
- "invalid-update-volume-mode-block-to-nil": {
- isExpectedFailure: true,
- oldClaim: validClaimVolumeModeBlock,
- newClaim: invalidClaimVolumeModeNil,
- enableResize: false,
- enableBlock: true,
- },
- "invalid-update-volume-mode-file-to-nil": {
- isExpectedFailure: true,
- oldClaim: validClaimVolumeModeFile,
- newClaim: invalidClaimVolumeModeNil,
- enableResize: false,
- enableBlock: true,
- },
- "invalid-update-volume-mode-empty-to-mode": {
- isExpectedFailure: true,
- oldClaim: validClaim,
- newClaim: validClaimVolumeModeBlock,
- enableResize: false,
- enableBlock: true,
- },
- "invalid-update-volume-mode-mode-to-empty": {
- isExpectedFailure: true,
- oldClaim: validClaimVolumeModeBlock,
- newClaim: validClaim,
- enableResize: false,
- enableBlock: true,
- },
- // with the new validation changes this test should not fail
- "noop-update-volumemode-allowed": {
- isExpectedFailure: false,
- oldClaim: validClaimVolumeModeFile,
- newClaim: validClaimVolumeModeFile,
- enableResize: false,
- enableBlock: false,
- },
- "invalid-update-change-storage-class-annotation-after-creation": {
- isExpectedFailure: true,
- oldClaim: validClaimStorageClass,
- newClaim: invalidUpdateClaimStorageClass,
- enableResize: false,
- enableBlock: false,
- },
- "valid-update-mutable-annotation": {
- isExpectedFailure: false,
- oldClaim: validClaimAnnotation,
- newClaim: validUpdateClaimMutableAnnotation,
- enableResize: false,
- enableBlock: false,
- },
- "valid-update-add-annotation": {
- isExpectedFailure: false,
- oldClaim: validClaim,
- newClaim: validAddClaimAnnotation,
- enableResize: false,
- enableBlock: false,
- },
- "valid-size-update-resize-disabled": {
- isExpectedFailure: true,
- oldClaim: validClaim,
- newClaim: validSizeUpdate,
- enableResize: false,
- enableBlock: false,
- },
- "valid-size-update-resize-enabled": {
- isExpectedFailure: false,
- oldClaim: validClaim,
- newClaim: validSizeUpdate,
- enableResize: true,
- enableBlock: false,
- },
- "invalid-size-update-resize-enabled": {
- isExpectedFailure: true,
- oldClaim: validClaim,
- newClaim: invalidSizeUpdate,
- enableResize: true,
- enableBlock: false,
- },
- "unbound-size-update-resize-enabled": {
- isExpectedFailure: true,
- oldClaim: validClaim,
- newClaim: unboundSizeUpdate,
- enableResize: true,
- enableBlock: false,
- },
- "valid-upgrade-storage-class-annotation-to-spec": {
- isExpectedFailure: false,
- oldClaim: validClaimStorageClass,
- newClaim: validClaimStorageClassInSpec,
- enableResize: false,
- enableBlock: false,
- },
- "invalid-upgrade-storage-class-annotation-to-spec": {
- isExpectedFailure: true,
- oldClaim: validClaimStorageClass,
- newClaim: invalidClaimStorageClassInSpec,
- enableResize: false,
- enableBlock: false,
- },
- "valid-upgrade-storage-class-annotation-to-annotation-and-spec": {
- isExpectedFailure: false,
- oldClaim: validClaimStorageClass,
- newClaim: validClaimStorageClassInAnnotationAndSpec,
- enableResize: false,
- enableBlock: false,
- },
- "invalid-upgrade-storage-class-annotation-to-annotation-and-spec": {
- isExpectedFailure: true,
- oldClaim: validClaimStorageClass,
- newClaim: invalidClaimStorageClassInAnnotationAndSpec,
- enableResize: false,
- enableBlock: false,
- },
- "invalid-upgrade-storage-class-in-spec": {
- isExpectedFailure: true,
- oldClaim: validClaimStorageClassInSpec,
- newClaim: invalidClaimStorageClassInSpec,
- enableResize: false,
- enableBlock: false,
- },
- "invalid-downgrade-storage-class-spec-to-annotation": {
- isExpectedFailure: true,
- oldClaim: validClaimStorageClassInSpec,
- newClaim: validClaimStorageClass,
- enableResize: false,
- enableBlock: false,
- },
- }
- for name, scenario := range scenarios {
- t.Run(name, func(t *testing.T) {
- // ensure we have a resource version specified for updates
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandPersistentVolumes, scenario.enableResize)()
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, scenario.enableBlock)()
- scenario.oldClaim.ResourceVersion = "1"
- scenario.newClaim.ResourceVersion = "1"
- errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim)
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- })
- }
- }
- func TestValidateKeyToPath(t *testing.T) {
- testCases := []struct {
- kp core.KeyToPath
- ok bool
- errtype field.ErrorType
- }{
- {
- kp: core.KeyToPath{Key: "k", Path: "p"},
- ok: true,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "p/p/p/p"},
- ok: true,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "p/..p/p../p..p"},
- ok: true,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32Ptr(0644)},
- ok: true,
- },
- {
- kp: core.KeyToPath{Key: "", Path: "p"},
- ok: false,
- errtype: field.ErrorTypeRequired,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: ""},
- ok: false,
- errtype: field.ErrorTypeRequired,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "..p"},
- ok: false,
- errtype: field.ErrorTypeInvalid,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "../p"},
- ok: false,
- errtype: field.ErrorTypeInvalid,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "p/../p"},
- ok: false,
- errtype: field.ErrorTypeInvalid,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "p/.."},
- ok: false,
- errtype: field.ErrorTypeInvalid,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32Ptr(01000)},
- ok: false,
- errtype: field.ErrorTypeInvalid,
- },
- {
- kp: core.KeyToPath{Key: "k", Path: "p", Mode: utilpointer.Int32Ptr(-1)},
- ok: false,
- errtype: field.ErrorTypeInvalid,
- },
- }
- for i, tc := range testCases {
- errs := validateKeyToPath(&tc.kp, field.NewPath("field"))
- if tc.ok && len(errs) > 0 {
- t.Errorf("[%d] unexpected errors: %v", i, errs)
- } else if !tc.ok && len(errs) == 0 {
- t.Errorf("[%d] expected error type %v", i, tc.errtype)
- } else if len(errs) > 1 {
- t.Errorf("[%d] expected only one error, got %d", i, len(errs))
- } else if !tc.ok {
- if errs[0].Type != tc.errtype {
- t.Errorf("[%d] expected error type %v, got %v", i, tc.errtype, errs[0].Type)
- }
- }
- }
- }
- func TestValidateNFSVolumeSource(t *testing.T) {
- testCases := []struct {
- name string
- nfs *core.NFSVolumeSource
- errtype field.ErrorType
- errfield string
- errdetail string
- }{
- {
- name: "missing server",
- nfs: &core.NFSVolumeSource{Server: "", Path: "/tmp"},
- errtype: field.ErrorTypeRequired,
- errfield: "server",
- },
- {
- name: "missing path",
- nfs: &core.NFSVolumeSource{Server: "my-server", Path: ""},
- errtype: field.ErrorTypeRequired,
- errfield: "path",
- },
- {
- name: "abs path",
- nfs: &core.NFSVolumeSource{Server: "my-server", Path: "tmp"},
- errtype: field.ErrorTypeInvalid,
- errfield: "path",
- errdetail: "must be an absolute path",
- },
- }
- for i, tc := range testCases {
- errs := validateNFSVolumeSource(tc.nfs, field.NewPath("field"))
- if len(errs) > 0 && tc.errtype == "" {
- t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
- } else if len(errs) == 0 && tc.errtype != "" {
- t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
- } else if len(errs) >= 1 {
- if errs[0].Type != tc.errtype {
- t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
- } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
- t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
- } else if !strings.Contains(errs[0].Detail, tc.errdetail) {
- t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail)
- }
- }
- }
- }
- func TestValidateGlusterfs(t *testing.T) {
- testCases := []struct {
- name string
- gfs *core.GlusterfsVolumeSource
- errtype field.ErrorType
- errfield string
- }{
- {
- name: "missing endpointname",
- gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: "/tmp"},
- errtype: field.ErrorTypeRequired,
- errfield: "endpoints",
- },
- {
- name: "missing path",
- gfs: &core.GlusterfsVolumeSource{EndpointsName: "my-endpoint", Path: ""},
- errtype: field.ErrorTypeRequired,
- errfield: "path",
- },
- {
- name: "missing endpointname and path",
- gfs: &core.GlusterfsVolumeSource{EndpointsName: "", Path: ""},
- errtype: field.ErrorTypeRequired,
- errfield: "endpoints",
- },
- }
- for i, tc := range testCases {
- errs := validateGlusterfsVolumeSource(tc.gfs, field.NewPath("field"))
- if len(errs) > 0 && tc.errtype == "" {
- t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
- } else if len(errs) == 0 && tc.errtype != "" {
- t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
- } else if len(errs) >= 1 {
- if errs[0].Type != tc.errtype {
- t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
- } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
- t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
- }
- }
- }
- }
- func TestValidateGlusterfsPersistentVolumeSource(t *testing.T) {
- var epNs *string
- namespace := ""
- epNs = &namespace
- testCases := []struct {
- name string
- gfs *core.GlusterfsPersistentVolumeSource
- errtype field.ErrorType
- errfield string
- }{
- {
- name: "missing endpointname",
- gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: "/tmp"},
- errtype: field.ErrorTypeRequired,
- errfield: "endpoints",
- },
- {
- name: "missing path",
- gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: ""},
- errtype: field.ErrorTypeRequired,
- errfield: "path",
- },
- {
- name: "non null endpointnamespace with empty string",
- gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "my-endpoint", Path: "/tmp", EndpointsNamespace: epNs},
- errtype: field.ErrorTypeInvalid,
- errfield: "endpointsNamespace",
- },
- {
- name: "missing endpointname and path",
- gfs: &core.GlusterfsPersistentVolumeSource{EndpointsName: "", Path: ""},
- errtype: field.ErrorTypeRequired,
- errfield: "endpoints",
- },
- }
- for i, tc := range testCases {
- errs := validateGlusterfsPersistentVolumeSource(tc.gfs, field.NewPath("field"))
- if len(errs) > 0 && tc.errtype == "" {
- t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
- } else if len(errs) == 0 && tc.errtype != "" {
- t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
- } else if len(errs) >= 1 {
- if errs[0].Type != tc.errtype {
- t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
- } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
- t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
- }
- }
- }
- }
- func TestValidateCSIVolumeSource(t *testing.T) {
- testCases := []struct {
- name string
- csi *core.CSIPersistentVolumeSource
- errtype field.ErrorType
- errfield string
- }{
- {
- name: "all required fields ok",
- csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
- },
- {
- name: "with default values ok",
- csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"},
- },
- {
- name: "missing driver name",
- csi: &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"},
- errtype: field.ErrorTypeRequired,
- errfield: "driver",
- },
- {
- name: "missing volume handle",
- csi: &core.CSIPersistentVolumeSource{Driver: "my-driver"},
- errtype: field.ErrorTypeRequired,
- errfield: "volumeHandle",
- },
- {
- name: "driver name: ok no punctuations",
- csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd", VolumeHandle: "test-123"},
- },
- {
- name: "driver name: ok dot only",
- csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage.csi.flex", VolumeHandle: "test-123"},
- },
- {
- name: "driver name: ok dash only",
- csi: &core.CSIPersistentVolumeSource{Driver: "io-kubernetes-storage-csi-flex", VolumeHandle: "test-123"},
- },
- {
- name: "driver name: invalid underscore",
- csi: &core.CSIPersistentVolumeSource{Driver: "io_kubernetes_storage_csi_flex", VolumeHandle: "test-123"},
- errtype: field.ErrorTypeInvalid,
- errfield: "driver",
- },
- {
- name: "driver name: invalid dot underscores",
- csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage_csi.flex", VolumeHandle: "test-123"},
- errtype: field.ErrorTypeInvalid,
- errfield: "driver",
- },
- {
- name: "driver name: ok beginnin with number",
- csi: &core.CSIPersistentVolumeSource{Driver: "2io.kubernetes.storage-csi.flex", VolumeHandle: "test-123"},
- },
- {
- name: "driver name: ok ending with number",
- csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes.storage-csi.flex2", VolumeHandle: "test-123"},
- },
- {
- name: "driver name: invalid dot dash underscores",
- csi: &core.CSIPersistentVolumeSource{Driver: "io.kubernetes-storage.csi_flex", VolumeHandle: "test-123"},
- errtype: field.ErrorTypeInvalid,
- errfield: "driver",
- },
- {
- name: "driver name: invalid length 0",
- csi: &core.CSIPersistentVolumeSource{Driver: "", VolumeHandle: "test-123"},
- errtype: field.ErrorTypeRequired,
- errfield: "driver",
- },
- {
- name: "driver name: ok length 1",
- csi: &core.CSIPersistentVolumeSource{Driver: "a", VolumeHandle: "test-123"},
- },
- {
- name: "driver name: invalid length > 63",
- csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepdcomgooglestoragecsigcepdcomgooglestoragecsigcepdcomgooglestoragecsigcepd", VolumeHandle: "test-123"},
- errtype: field.ErrorTypeTooLong,
- errfield: "driver",
- },
- {
- name: "driver name: invalid start char",
- csi: &core.CSIPersistentVolumeSource{Driver: "_comgooglestoragecsigcepd", VolumeHandle: "test-123"},
- errtype: field.ErrorTypeInvalid,
- errfield: "driver",
- },
- {
- name: "driver name: invalid end char",
- csi: &core.CSIPersistentVolumeSource{Driver: "comgooglestoragecsigcepd/", VolumeHandle: "test-123"},
- errtype: field.ErrorTypeInvalid,
- errfield: "driver",
- },
- {
- name: "driver name: invalid separators",
- csi: &core.CSIPersistentVolumeSource{Driver: "com/google/storage/csi~gcepd", VolumeHandle: "test-123"},
- errtype: field.ErrorTypeInvalid,
- errfield: "driver",
- },
- {
- name: "controllerExpandSecretRef: invalid name missing",
- csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Namespace: "default"}},
- errtype: field.ErrorTypeRequired,
- errfield: "controllerExpandSecretRef.name",
- },
- {
- name: "controllerExpandSecretRef: invalid namespace missing",
- csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar"}},
- errtype: field.ErrorTypeRequired,
- errfield: "controllerExpandSecretRef.namespace",
- },
- {
- name: "valid controllerExpandSecretRef",
- csi: &core.CSIPersistentVolumeSource{Driver: "com.google.gcepd", VolumeHandle: "foobar", ControllerExpandSecretRef: &core.SecretReference{Name: "foobar", Namespace: "default"}},
- },
- }
- for i, tc := range testCases {
- errs := validateCSIPersistentVolumeSource(tc.csi, field.NewPath("field"))
- if len(errs) > 0 && tc.errtype == "" {
- t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
- } else if len(errs) == 0 && tc.errtype != "" {
- t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
- } else if len(errs) >= 1 {
- if errs[0].Type != tc.errtype {
- t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
- } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
- t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
- }
- }
- }
- }
- // This test is a little too top-to-bottom. Ideally we would test each volume
- // type on its own, but we want to also make sure that the logic works through
- // the one-of wrapper, so we just do it all in one place.
- func TestValidateVolumes(t *testing.T) {
- validInitiatorName := "iqn.2015-02.example.com:init"
- invalidInitiatorName := "2015-02.example.com:init"
- type verr struct {
- etype field.ErrorType
- field string
- detail string
- }
- testCases := []struct {
- name string
- vol core.Volume
- errs []verr
- }{
- // EmptyDir and basic volume names
- {
- name: "valid alpha name",
- vol: core.Volume{
- Name: "empty",
- VolumeSource: core.VolumeSource{
- EmptyDir: &core.EmptyDirVolumeSource{},
- },
- },
- },
- {
- name: "valid num name",
- vol: core.Volume{
- Name: "123",
- VolumeSource: core.VolumeSource{
- EmptyDir: &core.EmptyDirVolumeSource{},
- },
- },
- },
- {
- name: "valid alphanum name",
- vol: core.Volume{
- Name: "empty-123",
- VolumeSource: core.VolumeSource{
- EmptyDir: &core.EmptyDirVolumeSource{},
- },
- },
- },
- {
- name: "valid numalpha name",
- vol: core.Volume{
- Name: "123-empty",
- VolumeSource: core.VolumeSource{
- EmptyDir: &core.EmptyDirVolumeSource{},
- },
- },
- },
- {
- name: "zero-length name",
- vol: core.Volume{
- Name: "",
- VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "name",
- }},
- },
- {
- name: "name > 63 characters",
- vol: core.Volume{
- Name: strings.Repeat("a", 64),
- VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "name",
- detail: "must be no more than",
- }},
- },
- {
- name: "name not a DNS label",
- vol: core.Volume{
- Name: "a.b.c",
- VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}},
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "name",
- detail: dnsLabelErrMsg,
- }},
- },
- // More than one source field specified.
- {
- name: "more than one source",
- vol: core.Volume{
- Name: "dups",
- VolumeSource: core.VolumeSource{
- EmptyDir: &core.EmptyDirVolumeSource{},
- HostPath: &core.HostPathVolumeSource{
- Path: "/mnt/path",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeForbidden,
- field: "hostPath",
- detail: "may not specify more than 1 volume",
- }},
- },
- // HostPath Default
- {
- name: "default HostPath",
- vol: core.Volume{
- Name: "hostpath",
- VolumeSource: core.VolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/mnt/path",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- },
- },
- // HostPath Supported
- {
- name: "valid HostPath",
- vol: core.Volume{
- Name: "hostpath",
- VolumeSource: core.VolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/mnt/path",
- Type: newHostPathType(string(core.HostPathSocket)),
- },
- },
- },
- },
- // HostPath Invalid
- {
- name: "invalid HostPath",
- vol: core.Volume{
- Name: "hostpath",
- VolumeSource: core.VolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/mnt/path",
- Type: newHostPathType("invalid"),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeNotSupported,
- field: "type",
- }},
- },
- {
- name: "invalid HostPath backsteps",
- vol: core.Volume{
- Name: "hostpath",
- VolumeSource: core.VolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/mnt/path/..",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "path",
- detail: "must not contain '..'",
- }},
- },
- // GcePersistentDisk
- {
- name: "valid GcePersistentDisk",
- vol: core.Volume{
- Name: "gce-pd",
- VolumeSource: core.VolumeSource{
- GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{
- PDName: "my-PD",
- FSType: "ext4",
- Partition: 1,
- ReadOnly: false,
- },
- },
- },
- },
- // AWSElasticBlockStore
- {
- name: "valid AWSElasticBlockStore",
- vol: core.Volume{
- Name: "aws-ebs",
- VolumeSource: core.VolumeSource{
- AWSElasticBlockStore: &core.AWSElasticBlockStoreVolumeSource{
- VolumeID: "my-PD",
- FSType: "ext4",
- Partition: 1,
- ReadOnly: false,
- },
- },
- },
- },
- // GitRepo
- {
- name: "valid GitRepo",
- vol: core.Volume{
- Name: "git-repo",
- VolumeSource: core.VolumeSource{
- GitRepo: &core.GitRepoVolumeSource{
- Repository: "my-repo",
- Revision: "hashstring",
- Directory: "target",
- },
- },
- },
- },
- {
- name: "valid GitRepo in .",
- vol: core.Volume{
- Name: "git-repo-dot",
- VolumeSource: core.VolumeSource{
- GitRepo: &core.GitRepoVolumeSource{
- Repository: "my-repo",
- Directory: ".",
- },
- },
- },
- },
- {
- name: "valid GitRepo with .. in name",
- vol: core.Volume{
- Name: "git-repo-dot-dot-foo",
- VolumeSource: core.VolumeSource{
- GitRepo: &core.GitRepoVolumeSource{
- Repository: "my-repo",
- Directory: "..foo",
- },
- },
- },
- },
- {
- name: "GitRepo starts with ../",
- vol: core.Volume{
- Name: "gitrepo",
- VolumeSource: core.VolumeSource{
- GitRepo: &core.GitRepoVolumeSource{
- Repository: "foo",
- Directory: "../dots/bar",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "gitRepo.directory",
- detail: `must not contain '..'`,
- }},
- },
- {
- name: "GitRepo contains ..",
- vol: core.Volume{
- Name: "gitrepo",
- VolumeSource: core.VolumeSource{
- GitRepo: &core.GitRepoVolumeSource{
- Repository: "foo",
- Directory: "dots/../bar",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "gitRepo.directory",
- detail: `must not contain '..'`,
- }},
- },
- {
- name: "GitRepo absolute target",
- vol: core.Volume{
- Name: "gitrepo",
- VolumeSource: core.VolumeSource{
- GitRepo: &core.GitRepoVolumeSource{
- Repository: "foo",
- Directory: "/abstarget",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "gitRepo.directory",
- }},
- },
- // ISCSI
- {
- name: "valid ISCSI",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "iqn.2015-02.example.com:test",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- },
- {
- name: "valid IQN: eui format",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "eui.0123456789ABCDEF",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- },
- {
- name: "valid IQN: naa format",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "naa.62004567BA64678D0123456789ABCDEF",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- },
- {
- name: "empty portal",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "",
- IQN: "iqn.2015-02.example.com:test",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "iscsi.targetPortal",
- }},
- },
- {
- name: "empty iqn",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "iscsi.iqn",
- }},
- },
- {
- name: "invalid IQN: iqn format",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "iqn.2015-02.example.com:test;ls;",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "iscsi.iqn",
- }},
- },
- {
- name: "invalid IQN: eui format",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "eui.0123456789ABCDEFGHIJ",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "iscsi.iqn",
- }},
- },
- {
- name: "invalid IQN: naa format",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "naa.62004567BA_4-78D.123456789ABCDEF",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "iscsi.iqn",
- }},
- },
- {
- name: "valid initiatorName",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "iqn.2015-02.example.com:test",
- Lun: 1,
- InitiatorName: &validInitiatorName,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- },
- {
- name: "invalid initiatorName",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "iqn.2015-02.example.com:test",
- Lun: 1,
- InitiatorName: &invalidInitiatorName,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "iscsi.initiatorname",
- }},
- },
- {
- name: "empty secret",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "iqn.2015-02.example.com:test",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- DiscoveryCHAPAuth: true,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "iscsi.secretRef",
- }},
- },
- {
- name: "empty secret",
- vol: core.Volume{
- Name: "iscsi",
- VolumeSource: core.VolumeSource{
- ISCSI: &core.ISCSIVolumeSource{
- TargetPortal: "127.0.0.1",
- IQN: "iqn.2015-02.example.com:test",
- Lun: 1,
- FSType: "ext4",
- ReadOnly: false,
- SessionCHAPAuth: true,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "iscsi.secretRef",
- }},
- },
- // Secret
- {
- name: "valid Secret",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "my-secret",
- },
- },
- },
- },
- {
- name: "valid Secret with defaultMode",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "my-secret",
- DefaultMode: utilpointer.Int32Ptr(0644),
- },
- },
- },
- },
- {
- name: "valid Secret with projection and mode",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "my-secret",
- Items: []core.KeyToPath{{
- Key: "key",
- Path: "filename",
- Mode: utilpointer.Int32Ptr(0644),
- }},
- },
- },
- },
- },
- {
- name: "valid Secret with subdir projection",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "my-secret",
- Items: []core.KeyToPath{{
- Key: "key",
- Path: "dir/filename",
- }},
- },
- },
- },
- },
- {
- name: "secret with missing path",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "s",
- Items: []core.KeyToPath{{Key: "key", Path: ""}},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "secret.items[0].path",
- }},
- },
- {
- name: "secret with leading ..",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "s",
- Items: []core.KeyToPath{{Key: "key", Path: "../foo"}},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "secret.items[0].path",
- }},
- },
- {
- name: "secret with .. inside",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "s",
- Items: []core.KeyToPath{{Key: "key", Path: "foo/../bar"}},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "secret.items[0].path",
- }},
- },
- {
- name: "secret with invalid positive defaultMode",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "s",
- DefaultMode: utilpointer.Int32Ptr(01000),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "secret.defaultMode",
- }},
- },
- {
- name: "secret with invalid negative defaultMode",
- vol: core.Volume{
- Name: "secret",
- VolumeSource: core.VolumeSource{
- Secret: &core.SecretVolumeSource{
- SecretName: "s",
- DefaultMode: utilpointer.Int32Ptr(-1),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "secret.defaultMode",
- }},
- },
- // ConfigMap
- {
- name: "valid ConfigMap",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{
- Name: "my-cfgmap",
- },
- },
- },
- },
- },
- {
- name: "valid ConfigMap with defaultMode",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{
- Name: "my-cfgmap",
- },
- DefaultMode: utilpointer.Int32Ptr(0644),
- },
- },
- },
- },
- {
- name: "valid ConfigMap with projection and mode",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{
- Name: "my-cfgmap"},
- Items: []core.KeyToPath{{
- Key: "key",
- Path: "filename",
- Mode: utilpointer.Int32Ptr(0644),
- }},
- },
- },
- },
- },
- {
- name: "valid ConfigMap with subdir projection",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{
- Name: "my-cfgmap"},
- Items: []core.KeyToPath{{
- Key: "key",
- Path: "dir/filename",
- }},
- },
- },
- },
- },
- {
- name: "configmap with missing path",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{Name: "c"},
- Items: []core.KeyToPath{{Key: "key", Path: ""}},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "configMap.items[0].path",
- }},
- },
- {
- name: "configmap with leading ..",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{Name: "c"},
- Items: []core.KeyToPath{{Key: "key", Path: "../foo"}},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "configMap.items[0].path",
- }},
- },
- {
- name: "configmap with .. inside",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{Name: "c"},
- Items: []core.KeyToPath{{Key: "key", Path: "foo/../bar"}},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "configMap.items[0].path",
- }},
- },
- {
- name: "configmap with invalid positive defaultMode",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{Name: "c"},
- DefaultMode: utilpointer.Int32Ptr(01000),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "configMap.defaultMode",
- }},
- },
- {
- name: "configmap with invalid negative defaultMode",
- vol: core.Volume{
- Name: "cfgmap",
- VolumeSource: core.VolumeSource{
- ConfigMap: &core.ConfigMapVolumeSource{
- LocalObjectReference: core.LocalObjectReference{Name: "c"},
- DefaultMode: utilpointer.Int32Ptr(-1),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "configMap.defaultMode",
- }},
- },
- // Glusterfs
- {
- name: "valid Glusterfs",
- vol: core.Volume{
- Name: "glusterfs",
- VolumeSource: core.VolumeSource{
- Glusterfs: &core.GlusterfsVolumeSource{
- EndpointsName: "host1",
- Path: "path",
- ReadOnly: false,
- },
- },
- },
- },
- {
- name: "empty hosts",
- vol: core.Volume{
- Name: "glusterfs",
- VolumeSource: core.VolumeSource{
- Glusterfs: &core.GlusterfsVolumeSource{
- EndpointsName: "",
- Path: "path",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "glusterfs.endpoints",
- }},
- },
- {
- name: "empty path",
- vol: core.Volume{
- Name: "glusterfs",
- VolumeSource: core.VolumeSource{
- Glusterfs: &core.GlusterfsVolumeSource{
- EndpointsName: "host",
- Path: "",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "glusterfs.path",
- }},
- },
- // Flocker
- {
- name: "valid Flocker -- datasetUUID",
- vol: core.Volume{
- Name: "flocker",
- VolumeSource: core.VolumeSource{
- Flocker: &core.FlockerVolumeSource{
- DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
- },
- },
- },
- },
- {
- name: "valid Flocker -- datasetName",
- vol: core.Volume{
- Name: "flocker",
- VolumeSource: core.VolumeSource{
- Flocker: &core.FlockerVolumeSource{
- DatasetName: "datasetName",
- },
- },
- },
- },
- {
- name: "both empty",
- vol: core.Volume{
- Name: "flocker",
- VolumeSource: core.VolumeSource{
- Flocker: &core.FlockerVolumeSource{
- DatasetName: "",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "flocker",
- }},
- },
- {
- name: "both specified",
- vol: core.Volume{
- Name: "flocker",
- VolumeSource: core.VolumeSource{
- Flocker: &core.FlockerVolumeSource{
- DatasetName: "datasetName",
- DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "flocker",
- }},
- },
- {
- name: "slash in flocker datasetName",
- vol: core.Volume{
- Name: "flocker",
- VolumeSource: core.VolumeSource{
- Flocker: &core.FlockerVolumeSource{
- DatasetName: "foo/bar",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "flocker.datasetName",
- detail: "must not contain '/'",
- }},
- },
- // RBD
- {
- name: "valid RBD",
- vol: core.Volume{
- Name: "rbd",
- VolumeSource: core.VolumeSource{
- RBD: &core.RBDVolumeSource{
- CephMonitors: []string{"foo"},
- RBDImage: "bar",
- FSType: "ext4",
- },
- },
- },
- },
- {
- name: "empty rbd monitors",
- vol: core.Volume{
- Name: "rbd",
- VolumeSource: core.VolumeSource{
- RBD: &core.RBDVolumeSource{
- CephMonitors: []string{},
- RBDImage: "bar",
- FSType: "ext4",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "rbd.monitors",
- }},
- },
- {
- name: "empty image",
- vol: core.Volume{
- Name: "rbd",
- VolumeSource: core.VolumeSource{
- RBD: &core.RBDVolumeSource{
- CephMonitors: []string{"foo"},
- RBDImage: "",
- FSType: "ext4",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "rbd.image",
- }},
- },
- // Cinder
- {
- name: "valid Cinder",
- vol: core.Volume{
- Name: "cinder",
- VolumeSource: core.VolumeSource{
- Cinder: &core.CinderVolumeSource{
- VolumeID: "29ea5088-4f60-4757-962e-dba678767887",
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- },
- // CephFS
- {
- name: "valid CephFS",
- vol: core.Volume{
- Name: "cephfs",
- VolumeSource: core.VolumeSource{
- CephFS: &core.CephFSVolumeSource{
- Monitors: []string{"foo"},
- },
- },
- },
- },
- {
- name: "empty cephfs monitors",
- vol: core.Volume{
- Name: "cephfs",
- VolumeSource: core.VolumeSource{
- CephFS: &core.CephFSVolumeSource{
- Monitors: []string{},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "cephfs.monitors",
- }},
- },
- // DownwardAPI
- {
- name: "valid DownwardAPI",
- vol: core.Volume{
- Name: "downwardapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{
- {
- Path: "labels",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- },
- {
- Path: "labels with subscript",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels['key']",
- },
- },
- {
- Path: "labels with complex subscript",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels['test.example.com/key']",
- },
- },
- {
- Path: "annotations",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.annotations",
- },
- },
- {
- Path: "annotations with subscript",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.annotations['key']",
- },
- },
- {
- Path: "annotations with complex subscript",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.annotations['TEST.EXAMPLE.COM/key']",
- },
- },
- {
- Path: "namespace",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.namespace",
- },
- },
- {
- Path: "name",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.name",
- },
- },
- {
- Path: "path/with/subdirs",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- },
- {
- Path: "path/./withdot",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- },
- {
- Path: "path/with/embedded..dotdot",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- },
- {
- Path: "path/with/leading/..dotdot",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- },
- {
- Path: "cpu_limit",
- ResourceFieldRef: &core.ResourceFieldSelector{
- ContainerName: "test-container",
- Resource: "limits.cpu",
- },
- },
- {
- Path: "cpu_request",
- ResourceFieldRef: &core.ResourceFieldSelector{
- ContainerName: "test-container",
- Resource: "requests.cpu",
- },
- },
- {
- Path: "memory_limit",
- ResourceFieldRef: &core.ResourceFieldSelector{
- ContainerName: "test-container",
- Resource: "limits.memory",
- },
- },
- {
- Path: "memory_request",
- ResourceFieldRef: &core.ResourceFieldSelector{
- ContainerName: "test-container",
- Resource: "requests.memory",
- },
- },
- },
- },
- },
- },
- },
- {
- name: "downapi valid defaultMode",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- DefaultMode: utilpointer.Int32Ptr(0644),
- },
- },
- },
- },
- {
- name: "downapi valid item mode",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Mode: utilpointer.Int32Ptr(0644),
- Path: "path",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- }},
- },
- },
- },
- },
- {
- name: "downapi invalid positive item mode",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Mode: utilpointer.Int32Ptr(01000),
- Path: "path",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- }},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI.mode",
- }},
- },
- {
- name: "downapi invalid negative item mode",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Mode: utilpointer.Int32Ptr(-1),
- Path: "path",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- }},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI.mode",
- }},
- },
- {
- name: "downapi empty metatada path",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Path: "",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- }},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "downwardAPI.path",
- }},
- },
- {
- name: "downapi absolute path",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Path: "/absolutepath",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- }},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI.path",
- }},
- },
- {
- name: "downapi dot dot path",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Path: "../../passwd",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- }},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI.path",
- detail: `must not contain '..'`,
- }},
- },
- {
- name: "downapi dot dot file name",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Path: "..badFileName",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- }},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI.path",
- detail: `must not start with '..'`,
- }},
- },
- {
- name: "downapi dot dot first level dirent",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Path: "..badDirName/goodFileName",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- }},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI.path",
- detail: `must not start with '..'`,
- }},
- },
- {
- name: "downapi fieldRef and ResourceFieldRef together",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- Items: []core.DownwardAPIVolumeFile{{
- Path: "test",
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels",
- },
- ResourceFieldRef: &core.ResourceFieldSelector{
- ContainerName: "test-container",
- Resource: "requests.memory",
- },
- }},
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI",
- detail: "fieldRef and resourceFieldRef can not be specified simultaneously",
- }},
- },
- {
- name: "downapi invalid positive defaultMode",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- DefaultMode: utilpointer.Int32Ptr(01000),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI.defaultMode",
- }},
- },
- {
- name: "downapi invalid negative defaultMode",
- vol: core.Volume{
- Name: "downapi",
- VolumeSource: core.VolumeSource{
- DownwardAPI: &core.DownwardAPIVolumeSource{
- DefaultMode: utilpointer.Int32Ptr(-1),
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "downwardAPI.defaultMode",
- }},
- },
- // FC
- {
- name: "FC valid targetWWNs and lun",
- vol: core.Volume{
- Name: "fc",
- VolumeSource: core.VolumeSource{
- FC: &core.FCVolumeSource{
- TargetWWNs: []string{"some_wwn"},
- Lun: utilpointer.Int32Ptr(1),
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- },
- {
- name: "FC valid wwids",
- vol: core.Volume{
- Name: "fc",
- VolumeSource: core.VolumeSource{
- FC: &core.FCVolumeSource{
- WWIDs: []string{"some_wwid"},
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- },
- {
- name: "FC empty targetWWNs and wwids",
- vol: core.Volume{
- Name: "fc",
- VolumeSource: core.VolumeSource{
- FC: &core.FCVolumeSource{
- TargetWWNs: []string{},
- Lun: utilpointer.Int32Ptr(1),
- WWIDs: []string{},
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "fc.targetWWNs",
- detail: "must specify either targetWWNs or wwids",
- }},
- },
- {
- name: "FC invalid: both targetWWNs and wwids simultaneously",
- vol: core.Volume{
- Name: "fc",
- VolumeSource: core.VolumeSource{
- FC: &core.FCVolumeSource{
- TargetWWNs: []string{"some_wwn"},
- Lun: utilpointer.Int32Ptr(1),
- WWIDs: []string{"some_wwid"},
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "fc.targetWWNs",
- detail: "targetWWNs and wwids can not be specified simultaneously",
- }},
- },
- {
- name: "FC valid targetWWNs and empty lun",
- vol: core.Volume{
- Name: "fc",
- VolumeSource: core.VolumeSource{
- FC: &core.FCVolumeSource{
- TargetWWNs: []string{"wwn"},
- Lun: nil,
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "fc.lun",
- detail: "lun is required if targetWWNs is specified",
- }},
- },
- {
- name: "FC valid targetWWNs and invalid lun",
- vol: core.Volume{
- Name: "fc",
- VolumeSource: core.VolumeSource{
- FC: &core.FCVolumeSource{
- TargetWWNs: []string{"wwn"},
- Lun: utilpointer.Int32Ptr(256),
- FSType: "ext4",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "fc.lun",
- detail: validation.InclusiveRangeError(0, 255),
- }},
- },
- // FlexVolume
- {
- name: "valid FlexVolume",
- vol: core.Volume{
- Name: "flex-volume",
- VolumeSource: core.VolumeSource{
- FlexVolume: &core.FlexVolumeSource{
- Driver: "kubernetes.io/blue",
- FSType: "ext4",
- },
- },
- },
- },
- // AzureFile
- {
- name: "valid AzureFile",
- vol: core.Volume{
- Name: "azure-file",
- VolumeSource: core.VolumeSource{
- AzureFile: &core.AzureFileVolumeSource{
- SecretName: "key",
- ShareName: "share",
- ReadOnly: false,
- },
- },
- },
- },
- {
- name: "AzureFile empty secret",
- vol: core.Volume{
- Name: "azure-file",
- VolumeSource: core.VolumeSource{
- AzureFile: &core.AzureFileVolumeSource{
- SecretName: "",
- ShareName: "share",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "azureFile.secretName",
- }},
- },
- {
- name: "AzureFile empty share",
- vol: core.Volume{
- Name: "azure-file",
- VolumeSource: core.VolumeSource{
- AzureFile: &core.AzureFileVolumeSource{
- SecretName: "name",
- ShareName: "",
- ReadOnly: false,
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "azureFile.shareName",
- }},
- },
- // Quobyte
- {
- name: "valid Quobyte",
- vol: core.Volume{
- Name: "quobyte",
- VolumeSource: core.VolumeSource{
- Quobyte: &core.QuobyteVolumeSource{
- Registry: "registry:7861",
- Volume: "volume",
- ReadOnly: false,
- User: "root",
- Group: "root",
- Tenant: "ThisIsSomeTenantUUID",
- },
- },
- },
- },
- {
- name: "empty registry quobyte",
- vol: core.Volume{
- Name: "quobyte",
- VolumeSource: core.VolumeSource{
- Quobyte: &core.QuobyteVolumeSource{
- Volume: "/test",
- Tenant: "ThisIsSomeTenantUUID",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "quobyte.registry",
- }},
- },
- {
- name: "wrong format registry quobyte",
- vol: core.Volume{
- Name: "quobyte",
- VolumeSource: core.VolumeSource{
- Quobyte: &core.QuobyteVolumeSource{
- Registry: "registry7861",
- Volume: "/test",
- Tenant: "ThisIsSomeTenantUUID",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "quobyte.registry",
- }},
- },
- {
- name: "wrong format multiple registries quobyte",
- vol: core.Volume{
- Name: "quobyte",
- VolumeSource: core.VolumeSource{
- Quobyte: &core.QuobyteVolumeSource{
- Registry: "registry:7861,reg2",
- Volume: "/test",
- Tenant: "ThisIsSomeTenantUUID",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeInvalid,
- field: "quobyte.registry",
- }},
- },
- {
- name: "empty volume quobyte",
- vol: core.Volume{
- Name: "quobyte",
- VolumeSource: core.VolumeSource{
- Quobyte: &core.QuobyteVolumeSource{
- Registry: "registry:7861",
- Tenant: "ThisIsSomeTenantUUID",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "quobyte.volume",
- }},
- },
- {
- name: "empty tenant quobyte",
- vol: core.Volume{
- Name: "quobyte",
- VolumeSource: core.VolumeSource{
- Quobyte: &core.QuobyteVolumeSource{
- Registry: "registry:7861",
- Volume: "/test",
- Tenant: "",
- },
- },
- },
- },
- {
- name: "too long tenant quobyte",
- vol: core.Volume{
- Name: "quobyte",
- VolumeSource: core.VolumeSource{
- Quobyte: &core.QuobyteVolumeSource{
- Registry: "registry:7861",
- Volume: "/test",
- Tenant: "this is too long to be a valid uuid so this test has to fail on the maximum length validation of the tenant.",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "quobyte.tenant",
- }},
- },
- // AzureDisk
- {
- name: "valid AzureDisk",
- vol: core.Volume{
- Name: "azure-disk",
- VolumeSource: core.VolumeSource{
- AzureDisk: &core.AzureDiskVolumeSource{
- DiskName: "foo",
- DataDiskURI: "https://blob/vhds/bar.vhd",
- },
- },
- },
- },
- {
- name: "AzureDisk empty disk name",
- vol: core.Volume{
- Name: "azure-disk",
- VolumeSource: core.VolumeSource{
- AzureDisk: &core.AzureDiskVolumeSource{
- DiskName: "",
- DataDiskURI: "https://blob/vhds/bar.vhd",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "azureDisk.diskName",
- }},
- },
- {
- name: "AzureDisk empty disk uri",
- vol: core.Volume{
- Name: "azure-disk",
- VolumeSource: core.VolumeSource{
- AzureDisk: &core.AzureDiskVolumeSource{
- DiskName: "foo",
- DataDiskURI: "",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "azureDisk.diskURI",
- }},
- },
- // ScaleIO
- {
- name: "valid scaleio volume",
- vol: core.Volume{
- Name: "scaleio-volume",
- VolumeSource: core.VolumeSource{
- ScaleIO: &core.ScaleIOVolumeSource{
- Gateway: "http://abcd/efg",
- System: "test-system",
- VolumeName: "test-vol-1",
- },
- },
- },
- },
- {
- name: "ScaleIO with empty name",
- vol: core.Volume{
- Name: "scaleio-volume",
- VolumeSource: core.VolumeSource{
- ScaleIO: &core.ScaleIOVolumeSource{
- Gateway: "http://abcd/efg",
- System: "test-system",
- VolumeName: "",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "scaleIO.volumeName",
- }},
- },
- {
- name: "ScaleIO with empty gateway",
- vol: core.Volume{
- Name: "scaleio-volume",
- VolumeSource: core.VolumeSource{
- ScaleIO: &core.ScaleIOVolumeSource{
- Gateway: "",
- System: "test-system",
- VolumeName: "test-vol-1",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "scaleIO.gateway",
- }},
- },
- {
- name: "ScaleIO with empty system",
- vol: core.Volume{
- Name: "scaleio-volume",
- VolumeSource: core.VolumeSource{
- ScaleIO: &core.ScaleIOVolumeSource{
- Gateway: "http://agc/efg/gateway",
- System: "",
- VolumeName: "test-vol-1",
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeRequired,
- field: "scaleIO.system",
- }},
- },
- // ProjectedVolumeSource
- {
- name: "ProjectedVolumeSource more than one projection in a source",
- vol: core.Volume{
- Name: "projected-volume",
- VolumeSource: core.VolumeSource{
- Projected: &core.ProjectedVolumeSource{
- Sources: []core.VolumeProjection{
- {
- Secret: &core.SecretProjection{
- LocalObjectReference: core.LocalObjectReference{
- Name: "foo",
- },
- },
- },
- {
- Secret: &core.SecretProjection{
- LocalObjectReference: core.LocalObjectReference{
- Name: "foo",
- },
- },
- DownwardAPI: &core.DownwardAPIProjection{},
- },
- },
- },
- },
- },
- errs: []verr{{
- etype: field.ErrorTypeForbidden,
- field: "projected.sources[1]",
- }},
- },
- {
- name: "ProjectedVolumeSource more than one projection in a source",
- vol: core.Volume{
- Name: "projected-volume",
- VolumeSource: core.VolumeSource{
- Projected: &core.ProjectedVolumeSource{
- Sources: []core.VolumeProjection{
- {
- Secret: &core.SecretProjection{},
- },
- {
- Secret: &core.SecretProjection{},
- DownwardAPI: &core.DownwardAPIProjection{},
- },
- },
- },
- },
- },
- errs: []verr{
- {
- etype: field.ErrorTypeRequired,
- field: "projected.sources[0].secret.name",
- },
- {
- etype: field.ErrorTypeRequired,
- field: "projected.sources[1].secret.name",
- },
- {
- etype: field.ErrorTypeForbidden,
- field: "projected.sources[1]",
- },
- },
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- names, errs := ValidateVolumes([]core.Volume{tc.vol}, field.NewPath("field"))
- if len(errs) != len(tc.errs) {
- t.Fatalf("unexpected error(s): got %d, want %d: %v", len(tc.errs), len(errs), errs)
- }
- if len(errs) == 0 && (len(names) > 1 || !IsMatchedVolume(tc.vol.Name, names)) {
- t.Errorf("wrong names result: %v", names)
- }
- for i, err := range errs {
- expErr := tc.errs[i]
- if err.Type != expErr.etype {
- t.Errorf("unexpected error type: got %v, want %v", expErr.etype, err.Type)
- }
- if !strings.HasSuffix(err.Field, "."+expErr.field) {
- t.Errorf("unexpected error field: got %v, want %v", expErr.field, err.Field)
- }
- if !strings.Contains(err.Detail, expErr.detail) {
- t.Errorf("unexpected error detail: got %v, want %v", expErr.detail, err.Detail)
- }
- }
- })
- }
- dupsCase := []core.Volume{
- {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
- {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
- }
- _, errs := ValidateVolumes(dupsCase, field.NewPath("field"))
- if len(errs) == 0 {
- t.Errorf("expected error")
- } else if len(errs) != 1 {
- t.Errorf("expected 1 error, got %d: %v", len(errs), errs)
- } else if errs[0].Type != field.ErrorTypeDuplicate {
- t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
- }
- // Validate HugePages medium type for EmptyDir
- hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}}
- // Enable HugePages
- if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working"); len(errs) != 0 {
- t.Errorf("Unexpected error when HugePages feature is enabled.")
- }
- }
- func TestHugePagesIsolation(t *testing.T) {
- successCases := []core.Pod{
- { // Basic fields.
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- },
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- }
- failureCases := []core.Pod{
- { // Basic fields.
- ObjectMeta: metav1.ObjectMeta{Name: "hugepages-requireCpuOrMemory", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- },
- Limits: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- { // Basic fields.
- ObjectMeta: metav1.ObjectMeta{Name: "hugepages-shared", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- },
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- { // Basic fields.
- ObjectMeta: metav1.ObjectMeta{Name: "hugepages-multiple", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
- },
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- }
- for i := range successCases {
- pod := &successCases[i]
- if errs := ValidatePod(pod); len(errs) != 0 {
- t.Errorf("Unexpected error for case[%d], err: %v", i, errs)
- }
- }
- for i := range failureCases {
- pod := &failureCases[i]
- if errs := ValidatePod(pod); len(errs) == 0 {
- t.Errorf("Expected error for case[%d], pod: %v", i, pod.Name)
- }
- }
- }
- func TestPVCVolumeMode(t *testing.T) {
- // Enable feature BlockVolume for PVC
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- block := core.PersistentVolumeBlock
- file := core.PersistentVolumeFilesystem
- fake := core.PersistentVolumeMode("fake")
- empty := core.PersistentVolumeMode("")
- // Success Cases
- successCasesPVC := map[string]*core.PersistentVolumeClaim{
- "valid block value": createTestVolModePVC(&block),
- "valid filesystem value": createTestVolModePVC(&file),
- "valid nil value": createTestVolModePVC(nil),
- }
- for k, v := range successCasesPVC {
- if errs := ValidatePersistentVolumeClaim(v); len(errs) != 0 {
- t.Errorf("expected success for %s", k)
- }
- }
- // Error Cases
- errorCasesPVC := map[string]*core.PersistentVolumeClaim{
- "invalid value": createTestVolModePVC(&fake),
- "empty value": createTestVolModePVC(&empty),
- }
- for k, v := range errorCasesPVC {
- if errs := ValidatePersistentVolumeClaim(v); len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- }
- }
- func TestPVVolumeMode(t *testing.T) {
- // Enable feature BlockVolume for PVC
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, true)()
- block := core.PersistentVolumeBlock
- file := core.PersistentVolumeFilesystem
- fake := core.PersistentVolumeMode("fake")
- empty := core.PersistentVolumeMode("")
- // Success Cases
- successCasesPV := map[string]*core.PersistentVolume{
- "valid block value": createTestVolModePV(&block),
- "valid filesystem value": createTestVolModePV(&file),
- "valid nil value": createTestVolModePV(nil),
- }
- for k, v := range successCasesPV {
- if errs := ValidatePersistentVolume(v); len(errs) != 0 {
- t.Errorf("expected success for %s", k)
- }
- }
- // Error Cases
- errorCasesPV := map[string]*core.PersistentVolume{
- "invalid value": createTestVolModePV(&fake),
- "empty value": createTestVolModePV(&empty),
- }
- for k, v := range errorCasesPV {
- if errs := ValidatePersistentVolume(v); len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- }
- }
- func createTestVolModePVC(vmode *core.PersistentVolumeMode) *core.PersistentVolumeClaim {
- validName := "valid-storage-class"
- pvc := core.PersistentVolumeClaim{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "default",
- },
- Spec: core.PersistentVolumeClaimSpec{
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- StorageClassName: &validName,
- VolumeMode: vmode,
- },
- }
- return &pvc
- }
- func createTestVolModePV(vmode *core.PersistentVolumeMode) *core.PersistentVolume {
- // PersistentVolume with VolumeMode set (valid and invalid)
- pv := core.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "",
- },
- Spec: core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- StorageClassName: "test-storage-class",
- VolumeMode: vmode,
- },
- }
- return &pv
- }
- func createTestPV() *core.PersistentVolume {
- // PersistentVolume with VolumeMode set (valid and invalid)
- pv := core.PersistentVolume{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "",
- },
- Spec: core.PersistentVolumeSpec{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
- PersistentVolumeSource: core.PersistentVolumeSource{
- HostPath: &core.HostPathVolumeSource{
- Path: "/foo",
- Type: newHostPathType(string(core.HostPathDirectory)),
- },
- },
- StorageClassName: "test-storage-class",
- },
- }
- return &pv
- }
- func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
- testCases := []core.VolumeSource{
- {EmptyDir: &core.EmptyDirVolumeSource{SizeLimit: resource.NewQuantity(int64(5), resource.BinarySI)}},
- }
- for _, tc := range testCases {
- if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol"); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- containerLimitCase := core.ResourceRequirements{
- Limits: core.ResourceList{
- core.ResourceEphemeralStorage: *resource.NewMilliQuantity(
- int64(40000),
- resource.BinarySI),
- },
- }
- if errs := ValidateResourceRequirements(&containerLimitCase, field.NewPath("resources")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- func TestValidateResourceQuotaWithAlphaLocalStorageCapacityIsolation(t *testing.T) {
- spec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- core.ResourceMemory: resource.MustParse("10000"),
- core.ResourceRequestsCPU: resource.MustParse("100"),
- core.ResourceRequestsMemory: resource.MustParse("10000"),
- core.ResourceLimitsCPU: resource.MustParse("100"),
- core.ResourceLimitsMemory: resource.MustParse("10000"),
- core.ResourcePods: resource.MustParse("10"),
- core.ResourceServices: resource.MustParse("0"),
- core.ResourceReplicationControllers: resource.MustParse("10"),
- core.ResourceQuotas: resource.MustParse("10"),
- core.ResourceConfigMaps: resource.MustParse("10"),
- core.ResourceSecrets: resource.MustParse("10"),
- core.ResourceEphemeralStorage: resource.MustParse("10000"),
- core.ResourceRequestsEphemeralStorage: resource.MustParse("10000"),
- core.ResourceLimitsEphemeralStorage: resource.MustParse("10000"),
- },
- }
- resourceQuota := &core.ResourceQuota{
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "foo",
- },
- Spec: spec,
- }
- if errs := ValidateResourceQuota(resourceQuota); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- func TestValidatePorts(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SCTPSupport, true)()
- successCase := []core.ContainerPort{
- {Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
- {Name: "easy", ContainerPort: 82, Protocol: "TCP"},
- {Name: "as", ContainerPort: 83, Protocol: "UDP"},
- {Name: "do-re-me", ContainerPort: 84, Protocol: "UDP"},
- {ContainerPort: 85, Protocol: "TCP"},
- }
- if errs := validateContainerPorts(successCase, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- nonCanonicalCase := []core.ContainerPort{
- {ContainerPort: 80, Protocol: "TCP"},
- }
- if errs := validateContainerPorts(nonCanonicalCase, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- errorCases := map[string]struct {
- P []core.ContainerPort
- T field.ErrorType
- F string
- D string
- }{
- "name > 15 characters": {
- []core.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}},
- field.ErrorTypeInvalid,
- "name", "15",
- },
- "name contains invalid characters": {
- []core.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}},
- field.ErrorTypeInvalid,
- "name", "alpha-numeric",
- },
- "name is a number": {
- []core.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}},
- field.ErrorTypeInvalid,
- "name", "at least one letter",
- },
- "name not unique": {
- []core.ContainerPort{
- {Name: "abc", ContainerPort: 80, Protocol: "TCP"},
- {Name: "abc", ContainerPort: 81, Protocol: "TCP"},
- },
- field.ErrorTypeDuplicate,
- "[1].name", "",
- },
- "zero container port": {
- []core.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}},
- field.ErrorTypeRequired,
- "containerPort", "",
- },
- "invalid container port": {
- []core.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}},
- field.ErrorTypeInvalid,
- "containerPort", "between",
- },
- "invalid host port": {
- []core.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}},
- field.ErrorTypeInvalid,
- "hostPort", "between",
- },
- "invalid protocol case": {
- []core.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}},
- field.ErrorTypeNotSupported,
- "protocol", `supported values: "SCTP", "TCP", "UDP"`,
- },
- "invalid protocol": {
- []core.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}},
- field.ErrorTypeNotSupported,
- "protocol", `supported values: "SCTP", "TCP", "UDP"`,
- },
- "protocol required": {
- []core.ContainerPort{{Name: "abc", ContainerPort: 80}},
- field.ErrorTypeRequired,
- "protocol", "",
- },
- }
- for k, v := range errorCases {
- errs := validateContainerPorts(v.P, field.NewPath("field"))
- if len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- for i := range errs {
- if errs[i].Type != v.T {
- t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type)
- }
- if !strings.Contains(errs[i].Field, v.F) {
- t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field)
- }
- if !strings.Contains(errs[i].Detail, v.D) {
- t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail)
- }
- }
- }
- }
- func TestLocalStorageEnvWithFeatureGate(t *testing.T) {
- testCases := []core.EnvVar{
- {
- Name: "ephemeral-storage-limits",
- ValueFrom: &core.EnvVarSource{
- ResourceFieldRef: &core.ResourceFieldSelector{
- ContainerName: "test-container",
- Resource: "limits.ephemeral-storage",
- },
- },
- },
- {
- Name: "ephemeral-storage-requests",
- ValueFrom: &core.EnvVarSource{
- ResourceFieldRef: &core.ResourceFieldSelector{
- ContainerName: "test-container",
- Resource: "requests.ephemeral-storage",
- },
- },
- },
- }
- for _, testCase := range testCases {
- if errs := validateEnvVarValueFrom(testCase, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success, got: %v", errs)
- }
- }
- }
- func TestValidateEnv(t *testing.T) {
- successCase := []core.EnvVar{
- {Name: "abc", Value: "value"},
- {Name: "ABC", Value: "value"},
- {Name: "AbC_123", Value: "value"},
- {Name: "abc", Value: ""},
- {Name: "a.b.c", Value: "value"},
- {Name: "a-b-c", Value: "value"},
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.annotations['key']",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.labels['key']",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.name",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.namespace",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.uid",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "spec.nodeName",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "spec.serviceAccountName",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "status.hostIP",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "status.podIP",
- },
- },
- },
- {
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "status.podIPs",
- },
- },
- },
- {
- Name: "secret_value",
- ValueFrom: &core.EnvVarSource{
- SecretKeyRef: &core.SecretKeySelector{
- LocalObjectReference: core.LocalObjectReference{
- Name: "some-secret",
- },
- Key: "secret-key",
- },
- },
- },
- {
- Name: "ENV_VAR_1",
- ValueFrom: &core.EnvVarSource{
- ConfigMapKeyRef: &core.ConfigMapKeySelector{
- LocalObjectReference: core.LocalObjectReference{
- Name: "some-config-map",
- },
- Key: "some-key",
- },
- },
- },
- }
- if errs := ValidateEnv(successCase, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success, got: %v", errs)
- }
- errorCases := []struct {
- name string
- envs []core.EnvVar
- expectedError string
- }{
- {
- name: "zero-length name",
- envs: []core.EnvVar{{Name: ""}},
- expectedError: "[0].name: Required value",
- },
- {
- name: "illegal character",
- envs: []core.EnvVar{{Name: "a!b"}},
- expectedError: `[0].name: Invalid value: "a!b": ` + envVarNameErrMsg,
- },
- {
- name: "dot only",
- envs: []core.EnvVar{{Name: "."}},
- expectedError: `[0].name: Invalid value: ".": must not be`,
- },
- {
- name: "double dots only",
- envs: []core.EnvVar{{Name: ".."}},
- expectedError: `[0].name: Invalid value: "..": must not be`,
- },
- {
- name: "leading double dots",
- envs: []core.EnvVar{{Name: "..abc"}},
- expectedError: `[0].name: Invalid value: "..abc": must not start with`,
- },
- {
- name: "value and valueFrom specified",
- envs: []core.EnvVar{{
- Name: "abc",
- Value: "foo",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.name",
- },
- },
- }},
- expectedError: "[0].valueFrom: Invalid value: \"\": may not be specified when `value` is not empty",
- },
- {
- name: "valueFrom without a source",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{},
- }},
- expectedError: "[0].valueFrom: Invalid value: \"\": must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`",
- },
- {
- name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.name",
- },
- SecretKeyRef: &core.SecretKeySelector{
- LocalObjectReference: core.LocalObjectReference{
- Name: "a-secret",
- },
- Key: "a-key",
- },
- },
- }},
- expectedError: "[0].valueFrom: Invalid value: \"\": may not have more than one field specified at a time",
- },
- {
- name: "valueFrom.fieldRef and valueFrom.configMapKeyRef set",
- envs: []core.EnvVar{{
- Name: "some_var_name",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.name",
- },
- ConfigMapKeyRef: &core.ConfigMapKeySelector{
- LocalObjectReference: core.LocalObjectReference{
- Name: "some-config-map",
- },
- Key: "some-key",
- },
- },
- }},
- expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
- },
- {
- name: "valueFrom.fieldRef and valueFrom.secretKeyRef specified",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- FieldPath: "metadata.name",
- },
- SecretKeyRef: &core.SecretKeySelector{
- LocalObjectReference: core.LocalObjectReference{
- Name: "a-secret",
- },
- Key: "a-key",
- },
- ConfigMapKeyRef: &core.ConfigMapKeySelector{
- LocalObjectReference: core.LocalObjectReference{
- Name: "some-config-map",
- },
- Key: "some-key",
- },
- },
- }},
- expectedError: `[0].valueFrom: Invalid value: "": may not have more than one field specified at a time`,
- },
- {
- name: "valueFrom.secretKeyRef.name invalid",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- SecretKeyRef: &core.SecretKeySelector{
- LocalObjectReference: core.LocalObjectReference{
- Name: "$%^&*#",
- },
- Key: "a-key",
- },
- },
- }},
- },
- {
- name: "valueFrom.configMapKeyRef.name invalid",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- ConfigMapKeyRef: &core.ConfigMapKeySelector{
- LocalObjectReference: core.LocalObjectReference{
- Name: "$%^&*#",
- },
- Key: "some-key",
- },
- },
- }},
- },
- {
- name: "missing FieldPath on ObjectFieldSelector",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- APIVersion: "v1",
- },
- },
- }},
- expectedError: `[0].valueFrom.fieldRef.fieldPath: Required value`,
- },
- {
- name: "missing APIVersion on ObjectFieldSelector",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- FieldPath: "metadata.name",
- },
- },
- }},
- expectedError: `[0].valueFrom.fieldRef.apiVersion: Required value`,
- },
- {
- name: "invalid fieldPath",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- FieldPath: "metadata.whoops",
- APIVersion: "v1",
- },
- },
- }},
- expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
- },
- {
- name: "metadata.name with subscript",
- envs: []core.EnvVar{{
- Name: "labels",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- FieldPath: "metadata.name['key']",
- APIVersion: "v1",
- },
- },
- }},
- expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
- },
- {
- name: "metadata.labels without subscript",
- envs: []core.EnvVar{{
- Name: "labels",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- FieldPath: "metadata.labels",
- APIVersion: "v1",
- },
- },
- }},
- expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`,
- },
- {
- name: "metadata.annotations without subscript",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- FieldPath: "metadata.annotations",
- APIVersion: "v1",
- },
- },
- }},
- expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`,
- },
- {
- name: "metadata.annotations with invalid key",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- FieldPath: "metadata.annotations['invalid~key']",
- APIVersion: "v1",
- },
- },
- }},
- expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
- },
- {
- name: "metadata.labels with invalid key",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- FieldPath: "metadata.labels['Www.k8s.io/test']",
- APIVersion: "v1",
- },
- },
- }},
- expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
- },
- {
- name: "unsupported fieldPath",
- envs: []core.EnvVar{{
- Name: "abc",
- ValueFrom: &core.EnvVarSource{
- FieldRef: &core.ObjectFieldSelector{
- FieldPath: "status.phase",
- APIVersion: "v1",
- },
- },
- }},
- expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP", "status.podIPs"`,
- },
- }
- for _, tc := range errorCases {
- if errs := ValidateEnv(tc.envs, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %s", tc.name)
- } else {
- for i := range errs {
- str := errs[i].Error()
- if str != "" && !strings.Contains(str, tc.expectedError) {
- t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
- }
- }
- }
- }
- }
- func TestValidateEnvFrom(t *testing.T) {
- successCase := []core.EnvFromSource{
- {
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"},
- },
- },
- {
- Prefix: "pre_",
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"},
- },
- },
- {
- Prefix: "a.b",
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"},
- },
- },
- {
- SecretRef: &core.SecretEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"},
- },
- },
- {
- Prefix: "pre_",
- SecretRef: &core.SecretEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"},
- },
- },
- {
- Prefix: "a.b",
- SecretRef: &core.SecretEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"},
- },
- },
- }
- if errs := ValidateEnvFrom(successCase, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- errorCases := []struct {
- name string
- envs []core.EnvFromSource
- expectedError string
- }{
- {
- name: "zero-length name",
- envs: []core.EnvFromSource{
- {
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: ""}},
- },
- },
- expectedError: "field[0].configMapRef.name: Required value",
- },
- {
- name: "invalid name",
- envs: []core.EnvFromSource{
- {
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "$"}},
- },
- },
- expectedError: "field[0].configMapRef.name: Invalid value",
- },
- {
- name: "invalid prefix",
- envs: []core.EnvFromSource{
- {
- Prefix: "a!b",
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
- },
- },
- expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg,
- },
- {
- name: "zero-length name",
- envs: []core.EnvFromSource{
- {
- SecretRef: &core.SecretEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: ""}},
- },
- },
- expectedError: "field[0].secretRef.name: Required value",
- },
- {
- name: "invalid name",
- envs: []core.EnvFromSource{
- {
- SecretRef: &core.SecretEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "&"}},
- },
- },
- expectedError: "field[0].secretRef.name: Invalid value",
- },
- {
- name: "invalid prefix",
- envs: []core.EnvFromSource{
- {
- Prefix: "a!b",
- SecretRef: &core.SecretEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
- },
- },
- expectedError: `field[0].prefix: Invalid value: "a!b": ` + envVarNameErrMsg,
- },
- {
- name: "no refs",
- envs: []core.EnvFromSource{
- {},
- },
- expectedError: "field: Invalid value: \"\": must specify one of: `configMapRef` or `secretRef`",
- },
- {
- name: "multiple refs",
- envs: []core.EnvFromSource{
- {
- SecretRef: &core.SecretEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "abc"}},
- },
- },
- expectedError: "field: Invalid value: \"\": may not have more than one field specified at a time",
- },
- {
- name: "invalid secret ref name",
- envs: []core.EnvFromSource{
- {
- SecretRef: &core.SecretEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
- },
- },
- expectedError: "field[0].secretRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
- },
- {
- name: "invalid config ref name",
- envs: []core.EnvFromSource{
- {
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "$%^&*#"}},
- },
- },
- expectedError: "field[0].configMapRef.name: Invalid value: \"$%^&*#\": " + dnsSubdomainLabelErrMsg,
- },
- }
- for _, tc := range errorCases {
- if errs := ValidateEnvFrom(tc.envs, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %s", tc.name)
- } else {
- for i := range errs {
- str := errs[i].Error()
- if str != "" && !strings.Contains(str, tc.expectedError) {
- t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
- }
- }
- }
- }
- }
- func TestValidateVolumeMounts(t *testing.T) {
- volumes := []core.Volume{
- {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
- {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
- {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
- }
- vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
- if len(v1err) > 0 {
- t.Errorf("Invalid test volume - expected success %v", v1err)
- return
- }
- container := core.Container{
- SecurityContext: nil,
- }
- propagation := core.MountPropagationBidirectional
- successCase := []core.VolumeMount{
- {Name: "abc", MountPath: "/foo"},
- {Name: "123", MountPath: "/bar"},
- {Name: "abc-123", MountPath: "/baz"},
- {Name: "abc-123", MountPath: "/baa", SubPath: ""},
- {Name: "abc-123", MountPath: "/bab", SubPath: "baz"},
- {Name: "abc-123", MountPath: "d:", SubPath: ""},
- {Name: "abc-123", MountPath: "F:", SubPath: ""},
- {Name: "abc-123", MountPath: "G:\\mount", SubPath: ""},
- {Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
- {Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
- }
- goodVolumeDevices := []core.VolumeDevice{
- {Name: "xyz", DevicePath: "/foofoo"},
- {Name: "uvw", DevicePath: "/foofoo/share/test"},
- }
- if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- errorCases := map[string][]core.VolumeMount{
- "empty name": {{Name: "", MountPath: "/foo"}},
- "name not found": {{Name: "", MountPath: "/foo"}},
- "empty mountpath": {{Name: "abc", MountPath: ""}},
- "mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
- "absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
- "subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
- "subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
- "subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
- "disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
- "name exists in volumeDevice": {{Name: "xyz", MountPath: "/bar"}},
- "mountpath exists in volumeDevice": {{Name: "uvw", MountPath: "/mnt/exists"}},
- "both exist in volumeDevice": {{Name: "xyz", MountPath: "/mnt/exists"}},
- }
- badVolumeDevice := []core.VolumeDevice{
- {Name: "xyz", DevicePath: "/mnt/exists"},
- }
- for k, v := range errorCases {
- if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- }
- }
- func TestValidateDisabledSubpath(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpath, false)()
- volumes := []core.Volume{
- {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
- {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
- {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
- }
- vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
- if len(v1err) > 0 {
- t.Errorf("Invalid test volume - expected success %v", v1err)
- return
- }
- container := core.Container{
- SecurityContext: nil,
- }
- goodVolumeDevices := []core.VolumeDevice{
- {Name: "xyz", DevicePath: "/foofoo"},
- {Name: "uvw", DevicePath: "/foofoo/share/test"},
- }
- cases := map[string]struct {
- mounts []core.VolumeMount
- expectError bool
- }{
- "subpath not specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- },
- },
- false,
- },
- "subpath specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- SubPath: "baz",
- },
- },
- false, // validation should not fail, dropping the field is handled in PrepareForCreate/PrepareForUpdate
- },
- }
- for name, test := range cases {
- errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
- if len(errs) != 0 && !test.expectError {
- t.Errorf("test %v failed: %+v", name, errs)
- }
- if len(errs) == 0 && test.expectError {
- t.Errorf("test %v failed, expected error", name)
- }
- }
- }
- func TestValidateSubpathMutuallyExclusive(t *testing.T) {
- // Enable feature VolumeSubpathEnvExpansion and VolumeSubpath
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpathEnvExpansion, true)()
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpath, true)()
- volumes := []core.Volume{
- {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
- {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
- {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
- }
- vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
- if len(v1err) > 0 {
- t.Errorf("Invalid test volume - expected success %v", v1err)
- return
- }
- container := core.Container{
- SecurityContext: nil,
- }
- goodVolumeDevices := []core.VolumeDevice{
- {Name: "xyz", DevicePath: "/foofoo"},
- {Name: "uvw", DevicePath: "/foofoo/share/test"},
- }
- cases := map[string]struct {
- mounts []core.VolumeMount
- expectError bool
- }{
- "subpath and subpathexpr not specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- },
- },
- false,
- },
- "subpath expr specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- SubPathExpr: "$(POD_NAME)",
- },
- },
- false,
- },
- "subpath specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- SubPath: "baz",
- },
- },
- false,
- },
- "subpath and subpathexpr specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- SubPath: "baz",
- SubPathExpr: "$(POD_NAME)",
- },
- },
- true,
- },
- }
- for name, test := range cases {
- errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
- if len(errs) != 0 && !test.expectError {
- t.Errorf("test %v failed: %+v", name, errs)
- }
- if len(errs) == 0 && test.expectError {
- t.Errorf("test %v failed, expected error", name)
- }
- }
- }
- func TestValidateDisabledSubpathExpr(t *testing.T) {
- // Enable feature VolumeSubpathEnvExpansion
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpathEnvExpansion, true)()
- volumes := []core.Volume{
- {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
- {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
- {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
- }
- vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
- if len(v1err) > 0 {
- t.Errorf("Invalid test volume - expected success %v", v1err)
- return
- }
- container := core.Container{
- SecurityContext: nil,
- }
- goodVolumeDevices := []core.VolumeDevice{
- {Name: "xyz", DevicePath: "/foofoo"},
- {Name: "uvw", DevicePath: "/foofoo/share/test"},
- }
- cases := map[string]struct {
- mounts []core.VolumeMount
- expectError bool
- }{
- "subpath expr not specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- },
- },
- false,
- },
- "subpath expr specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- SubPathExpr: "$(POD_NAME)",
- },
- },
- false,
- },
- }
- for name, test := range cases {
- errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
- if len(errs) != 0 && !test.expectError {
- t.Errorf("test %v failed: %+v", name, errs)
- }
- if len(errs) == 0 && test.expectError {
- t.Errorf("test %v failed, expected error", name)
- }
- }
- // Repeat with subpath feature gate off
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpath, false)()
- cases = map[string]struct {
- mounts []core.VolumeMount
- expectError bool
- }{
- "subpath expr not specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- },
- },
- false,
- },
- "subpath expr specified": {
- []core.VolumeMount{
- {
- Name: "abc-123",
- MountPath: "/bab",
- SubPathExpr: "$(POD_NAME)",
- },
- },
- false, // validation should not fail, dropping the field is handled in PrepareForCreate/PrepareForUpdate
- },
- }
- for name, test := range cases {
- errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
- if len(errs) != 0 && !test.expectError {
- t.Errorf("test %v failed: %+v", name, errs)
- }
- if len(errs) == 0 && test.expectError {
- t.Errorf("test %v failed, expected error", name)
- }
- }
- }
- func TestValidateMountPropagation(t *testing.T) {
- bTrue := true
- bFalse := false
- privilegedContainer := &core.Container{
- SecurityContext: &core.SecurityContext{
- Privileged: &bTrue,
- },
- }
- nonPrivilegedContainer := &core.Container{
- SecurityContext: &core.SecurityContext{
- Privileged: &bFalse,
- },
- }
- defaultContainer := &core.Container{}
- propagationBidirectional := core.MountPropagationBidirectional
- propagationHostToContainer := core.MountPropagationHostToContainer
- propagationNone := core.MountPropagationNone
- propagationInvalid := core.MountPropagationMode("invalid")
- tests := []struct {
- mount core.VolumeMount
- container *core.Container
- expectError bool
- }{
- {
- // implicitly non-privileged container + no propagation
- core.VolumeMount{Name: "foo", MountPath: "/foo"},
- defaultContainer,
- false,
- },
- {
- // implicitly non-privileged container + HostToContainer
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
- defaultContainer,
- false,
- },
- {
- // non-privileged container + None
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationNone},
- defaultContainer,
- false,
- },
- {
- // error: implicitly non-privileged container + Bidirectional
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
- defaultContainer,
- true,
- },
- {
- // explicitly non-privileged container + no propagation
- core.VolumeMount{Name: "foo", MountPath: "/foo"},
- nonPrivilegedContainer,
- false,
- },
- {
- // explicitly non-privileged container + HostToContainer
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
- nonPrivilegedContainer,
- false,
- },
- {
- // explicitly non-privileged container + HostToContainer
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
- nonPrivilegedContainer,
- true,
- },
- {
- // privileged container + no propagation
- core.VolumeMount{Name: "foo", MountPath: "/foo"},
- privilegedContainer,
- false,
- },
- {
- // privileged container + HostToContainer
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationHostToContainer},
- privilegedContainer,
- false,
- },
- {
- // privileged container + Bidirectional
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
- privilegedContainer,
- false,
- },
- {
- // error: privileged container + invalid mount propagation
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationInvalid},
- privilegedContainer,
- true,
- },
- {
- // no container + Bidirectional
- core.VolumeMount{Name: "foo", MountPath: "/foo", MountPropagation: &propagationBidirectional},
- nil,
- false,
- },
- }
- volumes := []core.Volume{
- {Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
- }
- vols2, v2err := ValidateVolumes(volumes, field.NewPath("field"))
- if len(v2err) > 0 {
- t.Errorf("Invalid test volume - expected success %v", v2err)
- return
- }
- for i, test := range tests {
- errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"))
- if test.expectError && len(errs) == 0 {
- t.Errorf("test %d expected error, got none", i)
- }
- if !test.expectError && len(errs) != 0 {
- t.Errorf("test %d expected success, got error: %v", i, errs)
- }
- }
- }
- func TestAlphaValidateVolumeDevices(t *testing.T) {
- volumes := []core.Volume{
- {Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
- {Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
- {Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
- }
- vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
- if len(v1err) > 0 {
- t.Errorf("Invalid test volumes - expected success %v", v1err)
- return
- }
- successCase := []core.VolumeDevice{
- {Name: "abc", DevicePath: "/foo"},
- {Name: "abc-123", DevicePath: "/usr/share/test"},
- }
- goodVolumeMounts := []core.VolumeMount{
- {Name: "xyz", MountPath: "/foofoo"},
- {Name: "ghi", MountPath: "/foo/usr/share/test"},
- }
- errorCases := map[string][]core.VolumeDevice{
- "empty name": {{Name: "", DevicePath: "/foo"}},
- "duplicate name": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc", DevicePath: "/foo/bar"}},
- "name not found": {{Name: "not-found", DevicePath: "/usr/share/test"}},
- "name found but invalid source": {{Name: "def", DevicePath: "/usr/share/test"}},
- "empty devicepath": {{Name: "abc", DevicePath: ""}},
- "relative devicepath": {{Name: "abc-123", DevicePath: "baz"}},
- "duplicate devicepath": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc-123", DevicePath: "/foo"}},
- "no backsteps": {{Name: "def", DevicePath: "/baz/../"}},
- "name exists in volumemounts": {{Name: "abc", DevicePath: "/baz/../"}},
- "path exists in volumemounts": {{Name: "xyz", DevicePath: "/this/path/exists"}},
- "both exist in volumemounts": {{Name: "abc", DevicePath: "/this/path/exists"}},
- }
- badVolumeMounts := []core.VolumeMount{
- {Name: "abc", MountPath: "/foo"},
- {Name: "abc-123", MountPath: "/this/path/exists"},
- }
- // Success Cases:
- // Validate normal success cases - only PVC volumeSource
- if errs := ValidateVolumeDevices(successCase, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- // Error Cases:
- // Validate normal error cases - only PVC volumeSource
- for k, v := range errorCases {
- if errs := ValidateVolumeDevices(v, GetVolumeMountMap(badVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- }
- }
- func TestValidateProbe(t *testing.T) {
- handler := core.Handler{Exec: &core.ExecAction{Command: []string{"echo"}}}
- // These fields must be positive.
- positiveFields := [...]string{"InitialDelaySeconds", "TimeoutSeconds", "PeriodSeconds", "SuccessThreshold", "FailureThreshold"}
- successCases := []*core.Probe{nil}
- for _, field := range positiveFields {
- probe := &core.Probe{Handler: handler}
- reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(10)
- successCases = append(successCases, probe)
- }
- for _, p := range successCases {
- if errs := validateProbe(p, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := []*core.Probe{{TimeoutSeconds: 10, InitialDelaySeconds: 10}}
- for _, field := range positiveFields {
- probe := &core.Probe{Handler: handler}
- reflect.ValueOf(probe).Elem().FieldByName(field).SetInt(-10)
- errorCases = append(errorCases, probe)
- }
- for _, p := range errorCases {
- if errs := validateProbe(p, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %v", p)
- }
- }
- }
- func TestValidateHandler(t *testing.T) {
- successCases := []core.Handler{
- {Exec: &core.ExecAction{Command: []string{"echo"}}},
- {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromInt(1), Host: "", Scheme: "HTTP"}},
- {HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt(65535), Host: "host", Scheme: "HTTP"}},
- {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}},
- {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host", Value: "foo.example.com"}}}},
- {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X-Forwarded-For", Value: "1.2.3.4"}, {Name: "X-Forwarded-For", Value: "5.6.7.8"}}}},
- }
- for _, h := range successCases {
- if errs := validateHandler(&h, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := []core.Handler{
- {},
- {Exec: &core.ExecAction{Command: []string{}}},
- {HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromInt(0), Host: ""}},
- {HTTPGet: &core.HTTPGetAction{Path: "/foo", Port: intstr.FromInt(65536), Host: "host"}},
- {HTTPGet: &core.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}},
- {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "Host:", Value: "foo.example.com"}}}},
- {HTTPGet: &core.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []core.HTTPHeader{{Name: "X_Forwarded_For", Value: "foo.example.com"}}}},
- }
- for _, h := range errorCases {
- if errs := validateHandler(&h, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %#v", h)
- }
- }
- }
- func TestValidatePullPolicy(t *testing.T) {
- type T struct {
- Container core.Container
- ExpectedPolicy core.PullPolicy
- }
- testCases := map[string]T{
- "NotPresent1": {
- core.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- core.PullIfNotPresent,
- },
- "NotPresent2": {
- core.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- core.PullIfNotPresent,
- },
- "Always1": {
- core.Container{Name: "123", Image: "image:latest", ImagePullPolicy: "Always"},
- core.PullAlways,
- },
- "Always2": {
- core.Container{Name: "1234", Image: "image", ImagePullPolicy: "Always"},
- core.PullAlways,
- },
- "Never1": {
- core.Container{Name: "abc-123", Image: "image:latest", ImagePullPolicy: "Never"},
- core.PullNever,
- },
- "Never2": {
- core.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"},
- core.PullNever,
- },
- }
- for k, v := range testCases {
- ctr := &v.Container
- errs := validatePullPolicy(ctr.ImagePullPolicy, field.NewPath("field"))
- if len(errs) != 0 {
- t.Errorf("case[%s] expected success, got %#v", k, errs)
- }
- if ctr.ImagePullPolicy != v.ExpectedPolicy {
- t.Errorf("case[%s] expected policy %v, got %v", k, v.ExpectedPolicy, ctr.ImagePullPolicy)
- }
- }
- }
- func getResourceLimits(cpu, memory string) core.ResourceList {
- res := core.ResourceList{}
- res[core.ResourceCPU] = resource.MustParse(cpu)
- res[core.ResourceMemory] = resource.MustParse(memory)
- return res
- }
- func TestValidateEphemeralContainers(t *testing.T) {
- containers := []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
- initContainers := []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
- vols := map[string]core.VolumeSource{"vol": {EmptyDir: &core.EmptyDirVolumeSource{}}}
- // Success Cases
- for title, ephemeralContainers := range map[string][]core.EphemeralContainer{
- "Empty Ephemeral Containers": {},
- "Single Container": {
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- "Multiple Containers": {
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug2", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- "Single Container with Target": {
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- TargetContainerName: "ctr",
- },
- },
- "All Whitelisted Fields": {
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debug",
- Image: "image",
- Command: []string{"bash"},
- Args: []string{"bash"},
- WorkingDir: "/",
- EnvFrom: []core.EnvFromSource{
- {
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{Name: "dummy"},
- Optional: &[]bool{true}[0],
- },
- },
- },
- Env: []core.EnvVar{
- {Name: "TEST", Value: "TRUE"},
- },
- VolumeMounts: []core.VolumeMount{
- {Name: "vol", MountPath: "/vol"},
- },
- TerminationMessagePath: "/dev/termination-log",
- TerminationMessagePolicy: "File",
- ImagePullPolicy: "IfNotPresent",
- Stdin: true,
- StdinOnce: true,
- TTY: true,
- },
- },
- },
- } {
- if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, field.NewPath("ephemeralContainers")); len(errs) != 0 {
- t.Errorf("expected success for '%s' but got errors: %v", title, errs)
- }
- }
- // Failure Cases
- tcs := []struct {
- title string
- ephemeralContainers []core.EphemeralContainer
- expectedError field.Error
- }{
- {
- "Name Collision with Container.Containers",
- []core.EphemeralContainer{
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- field.Error{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"},
- },
- {
- "Name Collision with Container.InitContainers",
- []core.EphemeralContainer{
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- field.Error{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"},
- },
- {
- "Name Collision with EphemeralContainers",
- []core.EphemeralContainer{
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- field.Error{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"},
- },
- {
- "empty Container Container",
- []core.EphemeralContainer{
- {EphemeralContainerCommon: core.EphemeralContainerCommon{}},
- },
- field.Error{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0]"},
- },
- {
- "empty Container Name",
- []core.EphemeralContainer{
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- field.Error{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0]"},
- },
- {
- "whitespace padded image name",
- []core.EphemeralContainer{
- {EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- field.Error{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0][0].image"},
- },
- {
- "TargetContainerName doesn't exist",
- []core.EphemeralContainer{
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- TargetContainerName: "bogus",
- },
- },
- field.Error{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"},
- },
- {
- "Container uses non-whitelisted field: Lifecycle",
- []core.EphemeralContainer{
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debug",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- Lifecycle: &core.Lifecycle{
- PreStop: &core.Handler{
- Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
- },
- },
- },
- },
- },
- field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"},
- },
- {
- "Container uses non-whitelisted field: LivenessProbe",
- []core.EphemeralContainer{
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debug",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- LivenessProbe: &core.Probe{
- Handler: core.Handler{
- TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt(80)},
- },
- SuccessThreshold: 1,
- },
- },
- },
- },
- field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"},
- },
- {
- "Container uses non-whitelisted field: Ports",
- []core.EphemeralContainer{
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debug",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- Ports: []core.ContainerPort{
- {Protocol: "TCP", ContainerPort: 80},
- },
- },
- },
- },
- field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"},
- },
- {
- "Container uses non-whitelisted field: ReadinessProbe",
- []core.EphemeralContainer{
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debug",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- ReadinessProbe: &core.Probe{
- Handler: core.Handler{
- TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt(80)},
- },
- },
- },
- },
- },
- field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"},
- },
- {
- "Container uses non-whitelisted field: Resources",
- []core.EphemeralContainer{
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debug",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- Resources: core.ResourceRequirements{
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- },
- },
- },
- },
- },
- field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"},
- },
- }
- for _, tc := range tcs {
- errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, field.NewPath("ephemeralContainers"))
- if len(errs) == 0 {
- t.Errorf("for test %q, expected error but received none", tc.title)
- } else if len(errs) > 1 {
- t.Errorf("for test %q, expected 1 error but received %d: %q", tc.title, len(errs), errs)
- } else {
- if errs[0].Type != tc.expectedError.Type {
- t.Errorf("for test %q, expected error type %q but received %q: %q", tc.title, string(tc.expectedError.Type), string(errs[0].Type), errs)
- }
- if errs[0].Field != tc.expectedError.Field {
- t.Errorf("for test %q, expected error for field %q but received error for field %q: %q", tc.title, tc.expectedError.Field, errs[0].Field, errs)
- }
- }
- }
- }
- func TestValidateContainers(t *testing.T) {
- volumeDevices := make(map[string]core.VolumeSource)
- capabilities.SetForTests(capabilities.Capabilities{
- AllowPrivileged: true,
- })
- successCase := []core.Container{
- {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- // backwards compatibility to ensure containers in pod template spec do not check for this
- {Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- {Name: "ghi", Image: " some ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- {
- Name: "life-123",
- Image: "image",
- Lifecycle: &core.Lifecycle{
- PreStop: &core.Handler{
- Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "resources-test",
- Image: "image",
- Resources: core.ResourceRequirements{
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("my.org/resource"): resource.MustParse("10"),
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "resources-test-with-request-and-limit",
- Image: "image",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- },
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "resources-request-limit-simple",
- Image: "image",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("8"),
- },
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "resources-request-limit-edge",
- Image: "image",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("my.org/resource"): resource.MustParse("10"),
- },
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("my.org/resource"): resource.MustParse("10"),
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "resources-request-limit-partials",
- Image: "image",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("9.5"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- },
- Limits: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName("my.org/resource"): resource.MustParse("10"),
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "resources-request",
- Image: "image",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("9.5"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "same-host-port-different-protocol",
- Image: "image",
- Ports: []core.ContainerPort{
- {ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
- {ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "fallback-to-logs-termination-message",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "FallbackToLogsOnError",
- },
- {
- Name: "file-termination-message",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "env-from-source",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- EnvFrom: []core.EnvFromSource{
- {
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{
- Name: "test",
- },
- },
- },
- },
- },
- {Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)},
- }
- if errs := validateContainers(successCase, false, volumeDevices, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- capabilities.SetForTests(capabilities.Capabilities{
- AllowPrivileged: false,
- })
- errorCases := map[string][]core.Container{
- "zero-length name": {{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- "zero-length-image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- "name > 63 characters": {{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- "name not a DNS label": {{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- "name not unique": {
- {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- },
- "zero-length image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- "host port not unique": {
- {Name: "abc", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}},
- ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- {Name: "def", Image: "image", Ports: []core.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}},
- ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- },
- "invalid env var name": {
- {Name: "abc", Image: "image", Env: []core.EnvVar{{Name: "ev!1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- },
- "unknown volume name": {
- {Name: "abc", Image: "image", VolumeMounts: []core.VolumeMount{{Name: "anything", MountPath: "/foo"}},
- ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
- },
- "invalid lifecycle, no exec command.": {
- {
- Name: "life-123",
- Image: "image",
- Lifecycle: &core.Lifecycle{
- PreStop: &core.Handler{
- Exec: &core.ExecAction{},
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "invalid lifecycle, no http path.": {
- {
- Name: "life-123",
- Image: "image",
- Lifecycle: &core.Lifecycle{
- PreStop: &core.Handler{
- HTTPGet: &core.HTTPGetAction{},
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "invalid lifecycle, no tcp socket port.": {
- {
- Name: "life-123",
- Image: "image",
- Lifecycle: &core.Lifecycle{
- PreStop: &core.Handler{
- TCPSocket: &core.TCPSocketAction{},
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "invalid lifecycle, zero tcp socket port.": {
- {
- Name: "life-123",
- Image: "image",
- Lifecycle: &core.Lifecycle{
- PreStop: &core.Handler{
- TCPSocket: &core.TCPSocketAction{
- Port: intstr.FromInt(0),
- },
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "invalid lifecycle, no action.": {
- {
- Name: "life-123",
- Image: "image",
- Lifecycle: &core.Lifecycle{
- PreStop: &core.Handler{},
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "invalid liveness probe, no tcp socket port.": {
- {
- Name: "life-123",
- Image: "image",
- LivenessProbe: &core.Probe{
- Handler: core.Handler{
- TCPSocket: &core.TCPSocketAction{},
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "invalid liveness probe, no action.": {
- {
- Name: "life-123",
- Image: "image",
- LivenessProbe: &core.Probe{
- Handler: core.Handler{},
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "invalid message termination policy": {
- {
- Name: "life-123",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "Unknown",
- },
- },
- "empty message termination policy": {
- {
- Name: "life-123",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "",
- },
- },
- "privilege disabled": {
- {Name: "abc", Image: "image", SecurityContext: fakeValidSecurityContext(true)},
- },
- "invalid compute resource": {
- {
- Name: "abc-123",
- Image: "image",
- Resources: core.ResourceRequirements{
- Limits: core.ResourceList{
- "disk": resource.MustParse("10G"),
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "Resource CPU invalid": {
- {
- Name: "abc-123",
- Image: "image",
- Resources: core.ResourceRequirements{
- Limits: getResourceLimits("-10", "0"),
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "Resource Requests CPU invalid": {
- {
- Name: "abc-123",
- Image: "image",
- Resources: core.ResourceRequirements{
- Requests: getResourceLimits("-10", "0"),
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "Resource Memory invalid": {
- {
- Name: "abc-123",
- Image: "image",
- Resources: core.ResourceRequirements{
- Limits: getResourceLimits("0", "-10"),
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "Request limit simple invalid": {
- {
- Name: "abc-123",
- Image: "image",
- Resources: core.ResourceRequirements{
- Limits: getResourceLimits("5", "3"),
- Requests: getResourceLimits("6", "3"),
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "Invalid storage limit request": {
- {
- Name: "abc-123",
- Image: "image",
- Resources: core.ResourceRequirements{
- Limits: core.ResourceList{
- core.ResourceName("attachable-volumes-aws-ebs"): *resource.NewQuantity(10, resource.DecimalSI),
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "Request limit multiple invalid": {
- {
- Name: "abc-123",
- Image: "image",
- Resources: core.ResourceRequirements{
- Limits: getResourceLimits("5", "3"),
- Requests: getResourceLimits("6", "4"),
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- "Invalid env from": {
- {
- Name: "env-from-source",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- EnvFrom: []core.EnvFromSource{
- {
- ConfigMapRef: &core.ConfigMapEnvSource{
- LocalObjectReference: core.LocalObjectReference{
- Name: "$%^&*#",
- },
- },
- },
- },
- },
- },
- }
- for k, v := range errorCases {
- if errs := validateContainers(v, false, volumeDevices, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- }
- }
- func TestValidateInitContainers(t *testing.T) {
- volumeDevices := make(map[string]core.VolumeSource)
- capabilities.SetForTests(capabilities.Capabilities{
- AllowPrivileged: true,
- })
- successCase := []core.Container{
- {
- Name: "container-1-same-host-port-different-protocol",
- Image: "image",
- Ports: []core.ContainerPort{
- {ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
- {ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- {
- Name: "container-2-same-host-port-different-protocol",
- Image: "image",
- Ports: []core.ContainerPort{
- {ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
- {ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }
- if errs := validateContainers(successCase, true, volumeDevices, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- capabilities.SetForTests(capabilities.Capabilities{
- AllowPrivileged: false,
- })
- errorCases := map[string][]core.Container{
- "duplicate ports": {
- {
- Name: "abc",
- Image: "image",
- Ports: []core.ContainerPort{
- {
- ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
- },
- {
- ContainerPort: 8080, HostPort: 8080, Protocol: "TCP",
- },
- },
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- },
- }
- for k, v := range errorCases {
- if errs := validateContainers(v, true, volumeDevices, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- }
- }
- func TestValidateRestartPolicy(t *testing.T) {
- successCases := []core.RestartPolicy{
- core.RestartPolicyAlways,
- core.RestartPolicyOnFailure,
- core.RestartPolicyNever,
- }
- for _, policy := range successCases {
- if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := []core.RestartPolicy{"", "newpolicy"}
- for k, policy := range errorCases {
- if errs := validateRestartPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %d", k)
- }
- }
- }
- func TestValidateDNSPolicy(t *testing.T) {
- successCases := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault, core.DNSPolicy(core.DNSClusterFirst), core.DNSNone}
- for _, policy := range successCases {
- if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := []core.DNSPolicy{core.DNSPolicy("invalid")}
- for _, policy := range errorCases {
- if errs := validateDNSPolicy(&policy, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %v", policy)
- }
- }
- }
- func TestValidatePodDNSConfig(t *testing.T) {
- generateTestSearchPathFunc := func(numChars int) string {
- res := ""
- for i := 0; i < numChars; i++ {
- res = res + "a"
- }
- return res
- }
- testOptionValue := "2"
- testDNSNone := core.DNSNone
- testDNSClusterFirst := core.DNSClusterFirst
- testCases := []struct {
- desc string
- dnsConfig *core.PodDNSConfig
- dnsPolicy *core.DNSPolicy
- expectedError bool
- }{
- {
- desc: "valid: empty DNSConfig",
- dnsConfig: &core.PodDNSConfig{},
- expectedError: false,
- },
- {
- desc: "valid: 1 option",
- dnsConfig: &core.PodDNSConfig{
- Options: []core.PodDNSConfigOption{
- {Name: "ndots", Value: &testOptionValue},
- },
- },
- expectedError: false,
- },
- {
- desc: "valid: 1 nameserver",
- dnsConfig: &core.PodDNSConfig{
- Nameservers: []string{"127.0.0.1"},
- },
- expectedError: false,
- },
- {
- desc: "valid: DNSNone with 1 nameserver",
- dnsConfig: &core.PodDNSConfig{
- Nameservers: []string{"127.0.0.1"},
- },
- dnsPolicy: &testDNSNone,
- expectedError: false,
- },
- {
- desc: "valid: 1 search path",
- dnsConfig: &core.PodDNSConfig{
- Searches: []string{"custom"},
- },
- expectedError: false,
- },
- {
- desc: "valid: 1 search path with trailing period",
- dnsConfig: &core.PodDNSConfig{
- Searches: []string{"custom."},
- },
- expectedError: false,
- },
- {
- desc: "valid: 3 nameservers and 6 search paths",
- dnsConfig: &core.PodDNSConfig{
- Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
- Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local."},
- },
- expectedError: false,
- },
- {
- desc: "valid: 256 characters in search path list",
- dnsConfig: &core.PodDNSConfig{
- // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
- Searches: []string{
- generateTestSearchPathFunc(1),
- generateTestSearchPathFunc(50),
- generateTestSearchPathFunc(50),
- generateTestSearchPathFunc(50),
- generateTestSearchPathFunc(50),
- generateTestSearchPathFunc(50),
- },
- },
- expectedError: false,
- },
- {
- desc: "valid: ipv6 nameserver",
- dnsConfig: &core.PodDNSConfig{
- Nameservers: []string{"FE80::0202:B3FF:FE1E:8329"},
- },
- expectedError: false,
- },
- {
- desc: "invalid: 4 nameservers",
- dnsConfig: &core.PodDNSConfig{
- Nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"},
- },
- expectedError: true,
- },
- {
- desc: "invalid: 7 search paths",
- dnsConfig: &core.PodDNSConfig{
- Searches: []string{"custom", "mydomain.com", "local", "cluster.local", "svc.cluster.local", "default.svc.cluster.local", "exceeded"},
- },
- expectedError: true,
- },
- {
- desc: "invalid: 257 characters in search path list",
- dnsConfig: &core.PodDNSConfig{
- // We can have 256 - (6 - 1) = 251 characters in total for 6 search paths.
- Searches: []string{
- generateTestSearchPathFunc(2),
- generateTestSearchPathFunc(50),
- generateTestSearchPathFunc(50),
- generateTestSearchPathFunc(50),
- generateTestSearchPathFunc(50),
- generateTestSearchPathFunc(50),
- },
- },
- expectedError: true,
- },
- {
- desc: "invalid search path",
- dnsConfig: &core.PodDNSConfig{
- Searches: []string{"custom?"},
- },
- expectedError: true,
- },
- {
- desc: "invalid nameserver",
- dnsConfig: &core.PodDNSConfig{
- Nameservers: []string{"invalid"},
- },
- expectedError: true,
- },
- {
- desc: "invalid empty option name",
- dnsConfig: &core.PodDNSConfig{
- Options: []core.PodDNSConfigOption{
- {Value: &testOptionValue},
- },
- },
- expectedError: true,
- },
- {
- desc: "invalid: DNSNone with 0 nameserver",
- dnsConfig: &core.PodDNSConfig{
- Searches: []string{"custom"},
- },
- dnsPolicy: &testDNSNone,
- expectedError: true,
- },
- }
- for _, tc := range testCases {
- if tc.dnsPolicy == nil {
- tc.dnsPolicy = &testDNSClusterFirst
- }
- errs := validatePodDNSConfig(tc.dnsConfig, tc.dnsPolicy, field.NewPath("dnsConfig"))
- if len(errs) != 0 && !tc.expectedError {
- t.Errorf("%v: validatePodDNSConfig(%v) = %v, want nil", tc.desc, tc.dnsConfig, errs)
- } else if len(errs) == 0 && tc.expectedError {
- t.Errorf("%v: validatePodDNSConfig(%v) = nil, want error", tc.desc, tc.dnsConfig)
- }
- }
- }
- func TestValidatePodReadinessGates(t *testing.T) {
- successCases := []struct {
- desc string
- readinessGates []core.PodReadinessGate
- }{
- {
- "no gate",
- []core.PodReadinessGate{},
- },
- {
- "one readiness gate",
- []core.PodReadinessGate{
- {
- ConditionType: core.PodConditionType("example.com/condition"),
- },
- },
- },
- {
- "two readiness gates",
- []core.PodReadinessGate{
- {
- ConditionType: core.PodConditionType("example.com/condition1"),
- },
- {
- ConditionType: core.PodConditionType("example.com/condition2"),
- },
- },
- },
- }
- for _, tc := range successCases {
- if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expect tc %q to success: %v", tc.desc, errs)
- }
- }
- errorCases := []struct {
- desc string
- readinessGates []core.PodReadinessGate
- }{
- {
- "invalid condition type",
- []core.PodReadinessGate{
- {
- ConditionType: core.PodConditionType("invalid/condition/type"),
- },
- },
- },
- }
- for _, tc := range errorCases {
- if errs := validateReadinessGates(tc.readinessGates, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected tc %q to fail", tc.desc)
- }
- }
- }
- func TestValidatePodConditions(t *testing.T) {
- successCases := []struct {
- desc string
- podConditions []core.PodCondition
- }{
- {
- "no condition",
- []core.PodCondition{},
- },
- {
- "one system condition",
- []core.PodCondition{
- {
- Type: core.PodReady,
- Status: core.ConditionTrue,
- },
- },
- },
- {
- "one system condition and one custom condition",
- []core.PodCondition{
- {
- Type: core.PodReady,
- Status: core.ConditionTrue,
- },
- {
- Type: core.PodConditionType("example.com/condition"),
- Status: core.ConditionFalse,
- },
- },
- },
- {
- "two custom condition",
- []core.PodCondition{
- {
- Type: core.PodConditionType("foobar"),
- Status: core.ConditionTrue,
- },
- {
- Type: core.PodConditionType("example.com/condition"),
- Status: core.ConditionFalse,
- },
- },
- },
- }
- for _, tc := range successCases {
- if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected tc %q to success, but got: %v", tc.desc, errs)
- }
- }
- errorCases := []struct {
- desc string
- podConditions []core.PodCondition
- }{
- {
- "one system condition and a invalid custom condition",
- []core.PodCondition{
- {
- Type: core.PodReady,
- Status: core.ConditionStatus("True"),
- },
- {
- Type: core.PodConditionType("invalid/custom/condition"),
- Status: core.ConditionStatus("True"),
- },
- },
- },
- }
- for _, tc := range errorCases {
- if errs := validatePodConditions(tc.podConditions, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected tc %q to fail", tc.desc)
- }
- }
- }
- func TestValidatePodSpec(t *testing.T) {
- activeDeadlineSeconds := int64(30)
- activeDeadlineSecondsMax := int64(math.MaxInt32)
- minUserID := int64(0)
- maxUserID := int64(2147483647)
- minGroupID := int64(0)
- maxGroupID := int64(2147483647)
- successCases := []core.PodSpec{
- { // Populate basic fields, leave defaults for most.
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate all fields.
- Volumes: []core.Volume{
- {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
- },
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- NodeSelector: map[string]string{
- "key": "value",
- },
- NodeName: "foobar",
- DNSPolicy: core.DNSClusterFirst,
- ActiveDeadlineSeconds: &activeDeadlineSeconds,
- ServiceAccountName: "acct",
- },
- { // Populate all fields with larger active deadline.
- Volumes: []core.Volume{
- {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
- },
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- InitContainers: []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- NodeSelector: map[string]string{
- "key": "value",
- },
- NodeName: "foobar",
- DNSPolicy: core.DNSClusterFirst,
- ActiveDeadlineSeconds: &activeDeadlineSecondsMax,
- ServiceAccountName: "acct",
- },
- { // Populate HostNetwork.
- Containers: []core.Container{
- {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
- Ports: []core.ContainerPort{
- {HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}},
- },
- },
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: true,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate RunAsUser SupplementalGroups FSGroup with minID 0
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- SupplementalGroups: []int64{minGroupID},
- RunAsUser: &minUserID,
- FSGroup: &minGroupID,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- SupplementalGroups: []int64{maxGroupID},
- RunAsUser: &maxUserID,
- FSGroup: &maxGroupID,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate HostIPC.
- SecurityContext: &core.PodSecurityContext{
- HostIPC: true,
- },
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate HostPID.
- SecurityContext: &core.PodSecurityContext{
- HostPID: true,
- },
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate Affinity.
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate HostAliases.
- HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}},
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate HostAliases with `foo.bar` hostnames.
- HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate HostAliases with HostNetwork.
- HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: true,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- { // Populate PriorityClassName.
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- PriorityClassName: "valid-name",
- },
- { // Populate ShareProcessNamespace
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- SecurityContext: &core.PodSecurityContext{
- ShareProcessNamespace: &[]bool{true}[0],
- },
- },
- { // Populate RuntimeClassName
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- RuntimeClassName: utilpointer.StringPtr("valid-sandbox"),
- },
- { // Populate Overhead
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- RuntimeClassName: utilpointer.StringPtr("valid-sandbox"),
- Overhead: core.ResourceList{},
- },
- }
- for i := range successCases {
- if errs := ValidatePodSpec(&successCases[i], field.NewPath("field")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- activeDeadlineSeconds = int64(0)
- activeDeadlineSecondsTooLarge := int64(math.MaxInt32 + 1)
- minUserID = int64(-1)
- maxUserID = int64(2147483648)
- minGroupID = int64(-1)
- maxGroupID = int64(2147483648)
- failureCases := map[string]core.PodSpec{
- "bad volume": {
- Volumes: []core.Volume{{}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- "no containers": {
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad container": {
- Containers: []core.Container{{}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad init container": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- InitContainers: []core.Container{{}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad DNS policy": {
- DNSPolicy: core.DNSPolicy("invalid"),
- RestartPolicy: core.RestartPolicyAlways,
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- "bad service account name": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- ServiceAccountName: "invalidName",
- },
- "bad restart policy": {
- RestartPolicy: "UnknowPolicy",
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- "with hostNetwork hostPort not equal to containerPort": {
- Containers: []core.Container{
- {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []core.ContainerPort{
- {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}},
- },
- },
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: true,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "with hostAliases with invalid IP": {
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: false,
- },
- HostAliases: []core.HostAlias{{IP: "999.999.999.999", Hostnames: []string{"host1", "host2"}}},
- },
- "with hostAliases with invalid hostname": {
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: false,
- },
- HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"@#$^#@#$"}}},
- },
- "bad supplementalGroups large than math.MaxInt32": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: false,
- SupplementalGroups: []int64{maxGroupID, 1234},
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad supplementalGroups less than 0": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: false,
- SupplementalGroups: []int64{minGroupID, 1234},
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad runAsUser large than math.MaxInt32": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: false,
- RunAsUser: &maxUserID,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad runAsUser less than 0": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: false,
- RunAsUser: &minUserID,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad fsGroup large than math.MaxInt32": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: false,
- FSGroup: &maxGroupID,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad fsGroup less than 0": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- SecurityContext: &core.PodSecurityContext{
- HostNetwork: false,
- FSGroup: &minGroupID,
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad-active-deadline-seconds": {
- Volumes: []core.Volume{
- {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
- },
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- NodeSelector: map[string]string{
- "key": "value",
- },
- NodeName: "foobar",
- DNSPolicy: core.DNSClusterFirst,
- ActiveDeadlineSeconds: &activeDeadlineSeconds,
- },
- "active-deadline-seconds-too-large": {
- Volumes: []core.Volume{
- {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
- },
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- NodeSelector: map[string]string{
- "key": "value",
- },
- NodeName: "foobar",
- DNSPolicy: core.DNSClusterFirst,
- ActiveDeadlineSeconds: &activeDeadlineSecondsTooLarge,
- },
- "bad nodeName": {
- NodeName: "node name",
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- "bad PriorityClassName": {
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- PriorityClassName: "InvalidName",
- },
- "ShareProcessNamespace and HostPID both set": {
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- SecurityContext: &core.PodSecurityContext{
- HostPID: true,
- ShareProcessNamespace: &[]bool{true}[0],
- },
- },
- "bad RuntimeClassName": {
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- RuntimeClassName: utilpointer.StringPtr("invalid/sandbox"),
- },
- }
- for k, v := range failureCases {
- if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("expected failure for %q", k)
- }
- }
- }
- func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec {
- var out core.PodSpec
- out.Containers = in.Containers
- out.RestartPolicy = in.RestartPolicy
- out.DNSPolicy = in.DNSPolicy
- out.Tolerations = tolerations
- return out
- }
- func TestValidatePod(t *testing.T) {
- validPodSpec := func(affinity *core.Affinity) core.PodSpec {
- spec := core.PodSpec{
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- }
- if affinity != nil {
- spec.Affinity = affinity
- }
- return spec
- }
- successCases := []core.Pod{
- { // Basic fields.
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- { // Just about everything.
- ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
- Spec: core.PodSpec{
- Volumes: []core.Volume{
- {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
- },
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- NodeSelector: map[string]string{
- "key": "value",
- },
- NodeName: "foobar",
- },
- },
- { // Serialized node affinity requirements.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(
- // TODO: Uncomment and move this block and move inside NodeAffinity once
- // RequiredDuringSchedulingRequiredDuringExecution is implemented
- // RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{
- // NodeSelectorTerms: []core.NodeSelectorTerm{
- // {
- // MatchExpressions: []core.NodeSelectorRequirement{
- // {
- // Key: "key1",
- // Operator: core.NodeSelectorOpExists
- // },
- // },
- // },
- // },
- // },
- &core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchExpressions: []core.NodeSelectorRequirement{
- {
- Key: "key2",
- Operator: core.NodeSelectorOpIn,
- Values: []string{"value1", "value2"},
- },
- },
- MatchFields: []core.NodeSelectorRequirement{
- {
- Key: "metadata.name",
- Operator: core.NodeSelectorOpIn,
- Values: []string{"host1"},
- },
- },
- },
- },
- },
- PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{
- {
- Weight: 10,
- Preference: core.NodeSelectorTerm{
- MatchExpressions: []core.NodeSelectorRequirement{
- {
- Key: "foo",
- Operator: core.NodeSelectorOpIn,
- Values: []string{"bar"},
- },
- },
- },
- },
- },
- },
- },
- ),
- },
- { // Serialized node affinity requirements.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(
- // TODO: Uncomment and move this block and move inside NodeAffinity once
- // RequiredDuringSchedulingRequiredDuringExecution is implemented
- // RequiredDuringSchedulingRequiredDuringExecution: &core.NodeSelector{
- // NodeSelectorTerms: []core.NodeSelectorTerm{
- // {
- // MatchExpressions: []core.NodeSelectorRequirement{
- // {
- // Key: "key1",
- // Operator: core.NodeSelectorOpExists
- // },
- // },
- // },
- // },
- // },
- &core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchExpressions: []core.NodeSelectorRequirement{},
- },
- },
- },
- PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{
- {
- Weight: 10,
- Preference: core.NodeSelectorTerm{
- MatchExpressions: []core.NodeSelectorRequirement{},
- },
- },
- },
- },
- },
- ),
- },
- { // Serialized pod affinity in affinity requirements in annotations.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- // TODO: Uncomment and move this block into Annotations map once
- // RequiredDuringSchedulingRequiredDuringExecution is implemented
- // "requiredDuringSchedulingRequiredDuringExecution": [{
- // "labelSelector": {
- // "matchExpressions": [{
- // "key": "key2",
- // "operator": "In",
- // "values": ["value1", "value2"]
- // }]
- // },
- // "namespaces":["ns"],
- // "topologyKey": "zone"
- // }]
- },
- Spec: validPodSpec(&core.Affinity{
- PodAffinity: &core.PodAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
- {
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpIn,
- Values: []string{"value1", "value2"},
- },
- },
- },
- TopologyKey: "zone",
- Namespaces: []string{"ns"},
- },
- },
- PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
- {
- Weight: 10,
- PodAffinityTerm: core.PodAffinityTerm{
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpNotIn,
- Values: []string{"value1", "value2"},
- },
- },
- },
- Namespaces: []string{"ns"},
- TopologyKey: "region",
- },
- },
- },
- },
- }),
- },
- { // Serialized pod anti affinity with different Label Operators in affinity requirements in annotations.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- // TODO: Uncomment and move this block into Annotations map once
- // RequiredDuringSchedulingRequiredDuringExecution is implemented
- // "requiredDuringSchedulingRequiredDuringExecution": [{
- // "labelSelector": {
- // "matchExpressions": [{
- // "key": "key2",
- // "operator": "In",
- // "values": ["value1", "value2"]
- // }]
- // },
- // "namespaces":["ns"],
- // "topologyKey": "zone"
- // }]
- },
- Spec: validPodSpec(&core.Affinity{
- PodAntiAffinity: &core.PodAntiAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
- {
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpExists,
- },
- },
- },
- TopologyKey: "zone",
- Namespaces: []string{"ns"},
- },
- },
- PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
- {
- Weight: 10,
- PodAffinityTerm: core.PodAffinityTerm{
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpDoesNotExist,
- },
- },
- },
- Namespaces: []string{"ns"},
- TopologyKey: "region",
- },
- },
- },
- },
- }),
- },
- { // populate forgiveness tolerations with exists operator in annotations.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
- },
- { // populate forgiveness tolerations with equal operator in annotations.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
- },
- { // populate tolerations equal operator in annotations.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
- },
- { // populate tolerations exists operator in annotations.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(nil),
- },
- { // empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}),
- },
- { // empty operator is OK for toleration, defaults to Equal.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
- },
- { // empty effect is OK for toleration, empty toleration effect means match all taint effects.
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}),
- },
- { // negative tolerationSeconds is OK for toleration.
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-forgiveness-invalid",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}),
- },
- { // runtime default seccomp profile
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompPodAnnotationKey: core.SeccompProfileRuntimeDefault,
- },
- },
- Spec: validPodSpec(nil),
- },
- { // docker default seccomp profile
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompPodAnnotationKey: core.DeprecatedSeccompProfileDockerDefault,
- },
- },
- Spec: validPodSpec(nil),
- },
- { // unconfined seccomp profile
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompPodAnnotationKey: "unconfined",
- },
- },
- Spec: validPodSpec(nil),
- },
- { // localhost seccomp profile
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompPodAnnotationKey: "localhost/foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- { // localhost seccomp profile for a container
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompContainerAnnotationKeyPrefix + "foo": "localhost/foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- { // default AppArmor profile for a container
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileRuntimeDefault,
- },
- },
- Spec: validPodSpec(nil),
- },
- { // default AppArmor profile for an init container
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- apparmor.ContainerAnnotationKeyPrefix + "init-ctr": apparmor.ProfileRuntimeDefault,
- },
- },
- Spec: core.PodSpec{
- InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- { // localhost AppArmor profile for a container
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileNamePrefix + "foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- { // syntactically valid sysctls
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: core.PodSpec{
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- SecurityContext: &core.PodSecurityContext{
- Sysctls: []core.Sysctl{
- {
- Name: "kernel.shmmni",
- Value: "32768",
- },
- {
- Name: "kernel.shmmax",
- Value: "1000000000",
- },
- {
- Name: "knet.ipv4.route.min_pmtu",
- Value: "1000",
- },
- },
- },
- },
- },
- { // valid extended resources for init container
- ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {
- Name: "valid-extended",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("10"),
- },
- Limits: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("10"),
- },
- },
- TerminationMessagePolicy: "File",
- },
- },
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- { // valid extended resources for regular container
- ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- Containers: []core.Container{
- {
- Name: "valid-extended",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("10"),
- },
- Limits: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("10"),
- },
- },
- TerminationMessagePolicy: "File",
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- { // valid serviceaccount token projected volume with serviceaccount name specified
- ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
- Spec: core.PodSpec{
- ServiceAccountName: "some-service-account",
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Volumes: []core.Volume{
- {
- Name: "projected-volume",
- VolumeSource: core.VolumeSource{
- Projected: &core.ProjectedVolumeSource{
- Sources: []core.VolumeProjection{
- {
- ServiceAccountToken: &core.ServiceAccountTokenProjection{
- Audience: "foo-audience",
- ExpirationSeconds: 6000,
- Path: "foo-path",
- },
- },
- },
- },
- },
- },
- },
- },
- },
- }
- for _, pod := range successCases {
- if errs := ValidatePod(&pod); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := map[string]struct {
- spec core.Pod
- expectedError string
- }{
- "bad name": {
- expectedError: "metadata.name",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "ns"},
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- },
- "image whitespace": {
- expectedError: "spec.containers[0].image",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- },
- "image leading and trailing whitespace": {
- expectedError: "spec.containers[0].image",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- },
- "bad namespace": {
- expectedError: "metadata.namespace",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""},
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- },
- "bad spec": {
- expectedError: "spec.containers[0].name",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{{}},
- },
- },
- },
- "bad label": {
- expectedError: "NoUppercaseOrSpecialCharsLike=Equals",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "ns",
- Labels: map[string]string{
- "NoUppercaseOrSpecialCharsLike=Equals": "bar",
- },
- },
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- },
- "invalid node selector requirement in node affinity, operator can't be null": {
- expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].operator",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchExpressions: []core.NodeSelectorRequirement{
- {
- Key: "key1",
- },
- },
- },
- },
- },
- },
- }),
- },
- },
- "invalid node selector requirement in node affinity, key is invalid": {
- expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].key",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchExpressions: []core.NodeSelectorRequirement{
- {
- Key: "invalid key ___@#",
- Operator: core.NodeSelectorOpExists,
- },
- },
- },
- },
- },
- },
- }),
- },
- },
- "invalid node field selector requirement in node affinity, more values for field selector": {
- expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].values",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchFields: []core.NodeSelectorRequirement{
- {
- Key: "metadata.name",
- Operator: core.NodeSelectorOpIn,
- Values: []string{"host1", "host2"},
- },
- },
- },
- },
- },
- },
- }),
- },
- },
- "invalid node field selector requirement in node affinity, invalid operator": {
- expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].operator",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchFields: []core.NodeSelectorRequirement{
- {
- Key: "metadata.name",
- Operator: core.NodeSelectorOpExists,
- },
- },
- },
- },
- },
- },
- }),
- },
- },
- "invalid node field selector requirement in node affinity, invalid key": {
- expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].key",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{
- {
- MatchFields: []core.NodeSelectorRequirement{
- {
- Key: "metadata.namespace",
- Operator: core.NodeSelectorOpIn,
- Values: []string{"ns1"},
- },
- },
- },
- },
- },
- },
- }),
- },
- },
- "invalid preferredSchedulingTerm in node affinity, weight should be in range 1-100": {
- expectedError: "must be in the range 1-100",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{
- {
- Weight: 199,
- Preference: core.NodeSelectorTerm{
- MatchExpressions: []core.NodeSelectorRequirement{
- {
- Key: "foo",
- Operator: core.NodeSelectorOpIn,
- Values: []string{"bar"},
- },
- },
- },
- },
- },
- },
- }),
- },
- },
- "invalid requiredDuringSchedulingIgnoredDuringExecution node selector, nodeSelectorTerms must have at least one term": {
- expectedError: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- NodeAffinity: &core.NodeAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
- NodeSelectorTerms: []core.NodeSelectorTerm{},
- },
- },
- }),
- },
- },
- "invalid weight in preferredDuringSchedulingIgnoredDuringExecution in pod affinity annotations, weight should be in range 1-100": {
- expectedError: "must be in the range 1-100",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- PodAffinity: &core.PodAffinity{
- PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
- {
- Weight: 109,
- PodAffinityTerm: core.PodAffinityTerm{
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpNotIn,
- Values: []string{"value1", "value2"},
- },
- },
- },
- Namespaces: []string{"ns"},
- TopologyKey: "region",
- },
- },
- },
- },
- }),
- },
- },
- "invalid labelSelector in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, values should be empty if the operator is Exists": {
- expectedError: "spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.matchExpressions.matchExpressions[0].values",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- PodAntiAffinity: &core.PodAntiAffinity{
- PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
- {
- Weight: 10,
- PodAffinityTerm: core.PodAffinityTerm{
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpExists,
- Values: []string{"value1", "value2"},
- },
- },
- },
- Namespaces: []string{"ns"},
- TopologyKey: "region",
- },
- },
- },
- },
- }),
- },
- },
- "invalid name space in preferredDuringSchedulingIgnoredDuringExecution in podaffinity annotations, name space shouldbe valid": {
- expectedError: "spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].podAffinityTerm.namespace",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- PodAffinity: &core.PodAffinity{
- PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
- {
- Weight: 10,
- PodAffinityTerm: core.PodAffinityTerm{
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpExists,
- },
- },
- },
- Namespaces: []string{"INVALID_NAMESPACE"},
- TopologyKey: "region",
- },
- },
- },
- },
- }),
- },
- },
- "invalid hard pod affinity, empty topologyKey is not allowed for hard pod affinity": {
- expectedError: "can not be empty",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- PodAffinity: &core.PodAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
- {
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpIn,
- Values: []string{"value1", "value2"},
- },
- },
- },
- Namespaces: []string{"ns"},
- },
- },
- },
- }),
- },
- },
- "invalid hard pod anti-affinity, empty topologyKey is not allowed for hard pod anti-affinity": {
- expectedError: "can not be empty",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- PodAntiAffinity: &core.PodAntiAffinity{
- RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
- {
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpIn,
- Values: []string{"value1", "value2"},
- },
- },
- },
- Namespaces: []string{"ns"},
- },
- },
- },
- }),
- },
- },
- "invalid soft pod affinity, empty topologyKey is not allowed for soft pod affinity": {
- expectedError: "can not be empty",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- PodAffinity: &core.PodAffinity{
- PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
- {
- Weight: 10,
- PodAffinityTerm: core.PodAffinityTerm{
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpNotIn,
- Values: []string{"value1", "value2"},
- },
- },
- },
- Namespaces: []string{"ns"},
- },
- },
- },
- },
- }),
- },
- },
- "invalid soft pod anti-affinity, empty topologyKey is not allowed for soft pod anti-affinity": {
- expectedError: "can not be empty",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: validPodSpec(&core.Affinity{
- PodAntiAffinity: &core.PodAntiAffinity{
- PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
- {
- Weight: 10,
- PodAffinityTerm: core.PodAffinityTerm{
- LabelSelector: &metav1.LabelSelector{
- MatchExpressions: []metav1.LabelSelectorRequirement{
- {
- Key: "key2",
- Operator: metav1.LabelSelectorOpNotIn,
- Values: []string{"value1", "value2"},
- },
- },
- },
- Namespaces: []string{"ns"},
- },
- },
- },
- },
- }),
- },
- },
- "invalid toleration key": {
- expectedError: "spec.tolerations[0].key",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "nospecialchars^=@", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
- },
- },
- "invalid toleration operator": {
- expectedError: "spec.tolerations[0].operator",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "In", Value: "bar", Effect: "NoSchedule"}}),
- },
- },
- "value must be empty when `operator` is 'Exists'": {
- expectedError: "spec.tolerations[0].operator",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}}),
- },
- },
- "operator must be 'Exists' when `key` is empty": {
- expectedError: "spec.tolerations[0].operator",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
- },
- },
- "effect must be 'NoExecute' when `TolerationSeconds` is set": {
- expectedError: "spec.tolerations[0].effect",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-forgiveness-invalid",
- Namespace: "ns",
- },
- Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoSchedule", TolerationSeconds: &[]int64{20}[0]}}),
- },
- },
- "must be a valid pod seccomp profile": {
- expectedError: "must be a valid seccomp profile",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompPodAnnotationKey: "foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- },
- "must be a valid container seccomp profile": {
- expectedError: "must be a valid seccomp profile",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompContainerAnnotationKeyPrefix + "foo": "foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- },
- "must be a non-empty container name in seccomp annotation": {
- expectedError: "name part must be non-empty",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompContainerAnnotationKeyPrefix: "foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- },
- "must be a non-empty container profile in seccomp annotation": {
- expectedError: "must be a valid seccomp profile",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompContainerAnnotationKeyPrefix + "foo": "",
- },
- },
- Spec: validPodSpec(nil),
- },
- },
- "must be a relative path in a node-local seccomp profile annotation": {
- expectedError: "must be a relative path",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompPodAnnotationKey: "localhost//foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- },
- "must not start with '../'": {
- expectedError: "must not contain '..'",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- core.SeccompPodAnnotationKey: "localhost/../foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- },
- "AppArmor profile must apply to a container": {
- expectedError: "metadata.annotations[container.apparmor.security.beta.kubernetes.io/fake-ctr]",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- apparmor.ContainerAnnotationKeyPrefix + "ctr": apparmor.ProfileRuntimeDefault,
- apparmor.ContainerAnnotationKeyPrefix + "init-ctr": apparmor.ProfileRuntimeDefault,
- apparmor.ContainerAnnotationKeyPrefix + "fake-ctr": apparmor.ProfileRuntimeDefault,
- },
- },
- Spec: core.PodSpec{
- InitContainers: []core.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "AppArmor profile format must be valid": {
- expectedError: "invalid AppArmor profile name",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- apparmor.ContainerAnnotationKeyPrefix + "ctr": "bad-name",
- },
- },
- Spec: validPodSpec(nil),
- },
- },
- "only default AppArmor profile may start with runtime/": {
- expectedError: "invalid AppArmor profile name",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Annotations: map[string]string{
- apparmor.ContainerAnnotationKeyPrefix + "ctr": "runtime/foo",
- },
- },
- Spec: validPodSpec(nil),
- },
- },
- "invalid extended resource name in container request": {
- expectedError: "must be a standard resource for containers",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "invalid",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("invalid-name"): resource.MustParse("2"),
- },
- Limits: core.ResourceList{
- core.ResourceName("invalid-name"): resource.MustParse("2"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "invalid extended resource requirement: request must be == limit": {
- expectedError: "must be equal to example.com/a",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "invalid",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("2"),
- },
- Limits: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("1"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "invalid extended resource requirement without limit": {
- expectedError: "Limit must be set",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "invalid",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("2"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "invalid fractional extended resource in container request": {
- expectedError: "must be an integer",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "invalid",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("500m"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "invalid fractional extended resource in init container request": {
- expectedError: "must be an integer",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {
- Name: "invalid",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("500m"),
- },
- },
- },
- },
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "invalid fractional extended resource in container limit": {
- expectedError: "must be an integer",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "invalid",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("5"),
- },
- Limits: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("2.5"),
- },
- },
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "invalid fractional extended resource in init container limit": {
- expectedError: "must be an integer",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {
- Name: "invalid",
- Image: "image",
- ImagePullPolicy: "IfNotPresent",
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("2.5"),
- },
- Limits: core.ResourceList{
- core.ResourceName("example.com/a"): resource.MustParse("2.5"),
- },
- },
- },
- },
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "mirror-pod present without nodeName": {
- expectedError: "mirror",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}},
- Spec: core.PodSpec{
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "mirror-pod populated without nodeName": {
- expectedError: "mirror",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}},
- Spec: core.PodSpec{
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- },
- },
- "serviceaccount token projected volume with no serviceaccount name specified": {
- expectedError: "must not be specified when serviceAccountName is not set",
- spec: core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
- Spec: core.PodSpec{
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Volumes: []core.Volume{
- {
- Name: "projected-volume",
- VolumeSource: core.VolumeSource{
- Projected: &core.ProjectedVolumeSource{
- Sources: []core.VolumeProjection{
- {
- ServiceAccountToken: &core.ServiceAccountTokenProjection{
- Audience: "foo-audience",
- ExpirationSeconds: 6000,
- Path: "foo-path",
- },
- },
- },
- },
- },
- },
- },
- },
- },
- },
- }
- for k, v := range errorCases {
- if errs := ValidatePod(&v.spec); len(errs) == 0 {
- t.Errorf("expected failure for %q", k)
- } else if v.expectedError == "" {
- t.Errorf("missing expectedError for %q, got %q", k, errs.ToAggregate().Error())
- } else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) {
- t.Errorf("expected error for %q to contain %q, got %q", k, v.expectedError, actualError)
- }
- }
- }
- func TestValidatePodUpdate(t *testing.T) {
- var (
- activeDeadlineSecondsZero = int64(0)
- activeDeadlineSecondsNegative = int64(-30)
- activeDeadlineSecondsPositive = int64(30)
- activeDeadlineSecondsLarger = int64(31)
- now = metav1.Now()
- grace = int64(30)
- grace2 = int64(31)
- )
- tests := []struct {
- new core.Pod
- old core.Pod
- err string
- test string
- }{
- {core.Pod{}, core.Pod{}, "", "nothing"},
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "bar"},
- },
- "metadata.name",
- "ids",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{
- "foo": "bar",
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{
- "bar": "foo",
- },
- },
- },
- "",
- "labels",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Annotations: map[string]string{
- "foo": "bar",
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Annotations: map[string]string{
- "bar": "foo",
- },
- },
- },
- "",
- "annotations",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V1",
- },
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V2",
- },
- {
- Image: "bar:V2",
- },
- },
- },
- },
- "may not add or remove containers",
- "less containers",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V1",
- },
- {
- Image: "bar:V2",
- },
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V2",
- },
- },
- },
- },
- "may not add or remove containers",
- "more containers",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {
- Image: "foo:V1",
- },
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {
- Image: "foo:V2",
- },
- {
- Image: "bar:V2",
- },
- },
- },
- },
- "may not add or remove containers",
- "more init containers",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
- Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
- },
- "metadata.deletionTimestamp",
- "deletion timestamp removed",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now},
- Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
- },
- "metadata.deletionTimestamp",
- "deletion timestamp added",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace},
- Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &grace2},
- Spec: core.PodSpec{Containers: []core.Container{{Image: "foo:V1"}}},
- },
- "metadata.deletionGracePeriodSeconds",
- "deletion grace period seconds changed",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V1",
- },
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V2",
- },
- },
- },
- },
- "",
- "image change",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {
- Image: "foo:V1",
- },
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {
- Image: "foo:V2",
- },
- },
- },
- },
- "",
- "init container image change",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {},
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V2",
- },
- },
- },
- },
- "spec.containers[0].image",
- "image change to empty",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {},
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- InitContainers: []core.Container{
- {
- Image: "foo:V2",
- },
- },
- },
- },
- "spec.initContainers[0].image",
- "init container image change to empty",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- EphemeralContainers: []core.EphemeralContainer{
- {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "ephemeral",
- Image: "busybox",
- },
- },
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{},
- },
- "Forbidden: pod updates may not change fields other than",
- "ephemeralContainer changes are not allowed via normal pod update",
- },
- {
- core.Pod{
- Spec: core.PodSpec{},
- },
- core.Pod{
- Spec: core.PodSpec{},
- },
- "",
- "activeDeadlineSeconds no change, nil",
- },
- {
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
- },
- },
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
- },
- },
- "",
- "activeDeadlineSeconds no change, set",
- },
- {
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
- },
- },
- core.Pod{},
- "",
- "activeDeadlineSeconds change to positive from nil",
- },
- {
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
- },
- },
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
- },
- },
- "",
- "activeDeadlineSeconds change to smaller positive",
- },
- {
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsLarger,
- },
- },
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
- },
- },
- "spec.activeDeadlineSeconds",
- "activeDeadlineSeconds change to larger positive",
- },
- {
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
- },
- },
- core.Pod{},
- "spec.activeDeadlineSeconds",
- "activeDeadlineSeconds change to negative from nil",
- },
- {
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsNegative,
- },
- },
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
- },
- },
- "spec.activeDeadlineSeconds",
- "activeDeadlineSeconds change to negative from positive",
- },
- {
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
- },
- },
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
- },
- },
- "",
- "activeDeadlineSeconds change to zero from positive",
- },
- {
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsZero,
- },
- },
- core.Pod{},
- "",
- "activeDeadlineSeconds change to zero from nil",
- },
- {
- core.Pod{},
- core.Pod{
- Spec: core.PodSpec{
- ActiveDeadlineSeconds: &activeDeadlineSecondsPositive,
- },
- },
- "spec.activeDeadlineSeconds",
- "activeDeadlineSeconds change to nil from positive",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V1",
- Resources: core.ResourceRequirements{
- Limits: getResourceLimits("100m", "0"),
- },
- },
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V2",
- Resources: core.ResourceRequirements{
- Limits: getResourceLimits("1000m", "0"),
- },
- },
- },
- },
- },
- "spec: Forbidden: pod updates may not change fields",
- "cpu change",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V1",
- Ports: []core.ContainerPort{
- {HostPort: 8080, ContainerPort: 80},
- },
- },
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Image: "foo:V2",
- Ports: []core.ContainerPort{
- {HostPort: 8000, ContainerPort: 80},
- },
- },
- },
- },
- },
- "spec: Forbidden: pod updates may not change fields",
- "port change",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{
- "foo": "bar",
- },
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{
- "Bar": "foo",
- },
- },
- },
- "",
- "bad label change",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
- },
- },
- "spec.tolerations: Forbidden",
- "existing toleration value modified in pod spec updates",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: nil}},
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
- },
- },
- "spec.tolerations: Forbidden",
- "existing toleration value modified in pod spec updates with modified tolerationSeconds",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]}},
- }},
- "",
- "modified tolerationSeconds in existing toleration value in pod spec updates",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- Tolerations: []core.Toleration{{Key: "key1", Value: "value2"}},
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
- },
- },
- "spec.tolerations: Forbidden",
- "toleration modified in updates to an unscheduled pod",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value1"}},
- },
- },
- "",
- "tolerations unmodified in updates to a scheduled pod",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{
- {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
- {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{30}[0]},
- },
- }},
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
- },
- },
- "",
- "added valid new toleration to existing tolerations in pod spec updates",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{
- NodeName: "node1",
- Tolerations: []core.Toleration{
- {Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{20}[0]},
- {Key: "key2", Value: "value2", Operator: "Equal", Effect: "NoSchedule", TolerationSeconds: &[]int64{30}[0]},
- },
- }},
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1", Tolerations: []core.Toleration{{Key: "key1", Value: "value1", Operator: "Equal", Effect: "NoExecute", TolerationSeconds: &[]int64{10}[0]}},
- }},
- "spec.tolerations[1].effect",
- "added invalid new toleration to existing tolerations in pod spec updates",
- },
- {
- core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
- core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
- "spec: Forbidden: pod updates may not change fields",
- "removed nodeName from pod spec",
- },
- {
- core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
- core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
- "metadata.annotations[kubernetes.io/config.mirror]",
- "added mirror pod annotation",
- },
- {
- core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: core.PodSpec{NodeName: "foo"}},
- core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: ""}}, Spec: core.PodSpec{NodeName: "foo"}},
- "metadata.annotations[kubernetes.io/config.mirror]",
- "removed mirror pod annotation",
- },
- {
- core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "foo"}}, Spec: core.PodSpec{NodeName: "foo"}},
- core.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Annotations: map[string]string{core.MirrorPodAnnotationKey: "bar"}}, Spec: core.PodSpec{NodeName: "foo"}},
- "metadata.annotations[kubernetes.io/config.mirror]",
- "changed mirror pod annotation",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- PriorityClassName: "bar-priority",
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- PriorityClassName: "foo-priority",
- },
- },
- "spec: Forbidden: pod updates",
- "changed priority class name",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- PriorityClassName: "",
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- PriorityClassName: "foo-priority",
- },
- },
- "spec: Forbidden: pod updates",
- "removed priority class name",
- },
- }
- for _, test := range tests {
- test.new.ObjectMeta.ResourceVersion = "1"
- test.old.ObjectMeta.ResourceVersion = "1"
- errs := ValidatePodUpdate(&test.new, &test.old)
- if test.err == "" {
- if len(errs) != 0 {
- t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
- }
- } else {
- if len(errs) == 0 {
- t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
- } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
- t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
- }
- }
- }
- }
- func TestValidatePodStatusUpdate(t *testing.T) {
- tests := []struct {
- new core.Pod
- old core.Pod
- err string
- test string
- }{
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- },
- Status: core.PodStatus{
- NominatedNodeName: "node1",
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- },
- Status: core.PodStatus{},
- },
- "",
- "removed nominatedNodeName",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- },
- Status: core.PodStatus{
- NominatedNodeName: "node1",
- },
- },
- "",
- "add valid nominatedNodeName",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- },
- Status: core.PodStatus{
- NominatedNodeName: "Node1",
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- },
- },
- "nominatedNodeName",
- "Add invalid nominatedNodeName",
- },
- {
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- },
- Status: core.PodStatus{
- NominatedNodeName: "node1",
- },
- },
- core.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.PodSpec{
- NodeName: "node1",
- },
- Status: core.PodStatus{
- NominatedNodeName: "node2",
- },
- },
- "",
- "Update nominatedNodeName",
- },
- }
- for _, test := range tests {
- test.new.ObjectMeta.ResourceVersion = "1"
- test.old.ObjectMeta.ResourceVersion = "1"
- errs := ValidatePodStatusUpdate(&test.new, &test.old)
- if test.err == "" {
- if len(errs) != 0 {
- t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
- }
- } else {
- if len(errs) == 0 {
- t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
- } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
- t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
- }
- }
- }
- }
- func makeValidService() core.Service {
- serviceIPFamily := core.IPv4Protocol
- return core.Service{
- ObjectMeta: metav1.ObjectMeta{
- Name: "valid",
- Namespace: "valid",
- Labels: map[string]string{},
- Annotations: map[string]string{},
- ResourceVersion: "1",
- },
- Spec: core.ServiceSpec{
- Selector: map[string]string{"key": "val"},
- SessionAffinity: "None",
- Type: core.ServiceTypeClusterIP,
- Ports: []core.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt(8675)}},
- IPFamily: &serviceIPFamily,
- },
- }
- }
- func TestValidatePodEphemeralContainersUpdate(t *testing.T) {
- tests := []struct {
- new []core.EphemeralContainer
- old []core.EphemeralContainer
- err string
- test string
- }{
- {[]core.EphemeralContainer{}, []core.EphemeralContainer{}, "", "nothing"},
- {
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- "",
- "No change in Ephemeral Containers",
- },
- {
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- "",
- "Ephemeral Container list order changes",
- },
- {
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- []core.EphemeralContainer{},
- "",
- "Add an Ephemeral Container",
- },
- {
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger1",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- []core.EphemeralContainer{},
- "",
- "Add two Ephemeral Containers",
- },
- {
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- "",
- "Add to an existing Ephemeral Containers",
- },
- {
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger3",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- "",
- "Add to an existing Ephemeral Containers, list order changes",
- },
- {
- []core.EphemeralContainer{},
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- "may not be removed",
- "Remove an Ephemeral Container",
- },
- {
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "firstone",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "thentheother",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- "may not be removed",
- "Replace an Ephemeral Container",
- },
- {
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger1",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- []core.EphemeralContainer{{
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger1",
- Image: "debian",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }, {
- EphemeralContainerCommon: core.EphemeralContainerCommon{
- Name: "debugger2",
- Image: "busybox",
- ImagePullPolicy: "IfNotPresent",
- TerminationMessagePolicy: "File",
- },
- }},
- "may not be changed",
- "Change an Ephemeral Containers",
- },
- }
- for _, test := range tests {
- new := core.Pod{Spec: core.PodSpec{EphemeralContainers: test.new}}
- old := core.Pod{Spec: core.PodSpec{EphemeralContainers: test.old}}
- errs := ValidatePodEphemeralContainersUpdate(&new, &old)
- if test.err == "" {
- if len(errs) != 0 {
- t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
- }
- } else {
- if len(errs) == 0 {
- t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
- } else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
- t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
- }
- }
- }
- }
- func TestValidateService(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SCTPSupport, true)()
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceTopology, true)()
- testCases := []struct {
- name string
- tweakSvc func(svc *core.Service) // given a basic valid service, each test case can customize it
- numErrs int
- }{
- {
- name: "missing namespace",
- tweakSvc: func(s *core.Service) {
- s.Namespace = ""
- },
- numErrs: 1,
- },
- {
- name: "invalid namespace",
- tweakSvc: func(s *core.Service) {
- s.Namespace = "-123"
- },
- numErrs: 1,
- },
- {
- name: "missing name",
- tweakSvc: func(s *core.Service) {
- s.Name = ""
- },
- numErrs: 1,
- },
- {
- name: "invalid name",
- tweakSvc: func(s *core.Service) {
- s.Name = "-123"
- },
- numErrs: 1,
- },
- {
- name: "too long name",
- tweakSvc: func(s *core.Service) {
- s.Name = strings.Repeat("a", 64)
- },
- numErrs: 1,
- },
- {
- name: "invalid generateName",
- tweakSvc: func(s *core.Service) {
- s.GenerateName = "-123"
- },
- numErrs: 1,
- },
- {
- name: "too long generateName",
- tweakSvc: func(s *core.Service) {
- s.GenerateName = strings.Repeat("a", 64)
- },
- numErrs: 1,
- },
- {
- name: "invalid label",
- tweakSvc: func(s *core.Service) {
- s.Labels["NoUppercaseOrSpecialCharsLike=Equals"] = "bar"
- },
- numErrs: 1,
- },
- {
- name: "invalid annotation",
- tweakSvc: func(s *core.Service) {
- s.Annotations["NoSpecialCharsLike=Equals"] = "bar"
- },
- numErrs: 1,
- },
- {
- name: "nil selector",
- tweakSvc: func(s *core.Service) {
- s.Spec.Selector = nil
- },
- numErrs: 0,
- },
- {
- name: "invalid selector",
- tweakSvc: func(s *core.Service) {
- s.Spec.Selector["NoSpecialCharsLike=Equals"] = "bar"
- },
- numErrs: 1,
- },
- {
- name: "missing session affinity",
- tweakSvc: func(s *core.Service) {
- s.Spec.SessionAffinity = ""
- },
- numErrs: 1,
- },
- {
- name: "missing type",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = ""
- },
- numErrs: 1,
- },
- {
- name: "missing ports",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports = nil
- },
- numErrs: 1,
- },
- {
- name: "missing ports but headless",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports = nil
- s.Spec.ClusterIP = core.ClusterIPNone
- },
- numErrs: 0,
- },
- {
- name: "empty port[0] name",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Name = ""
- },
- numErrs: 0,
- },
- {
- name: "empty port[1] name",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 1,
- },
- {
- name: "empty multi-port port[0] name",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Name = ""
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Protocol: "TCP", Port: 12345, TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 1,
- },
- {
- name: "invalid port name",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Name = "INVALID"
- },
- numErrs: 1,
- },
- {
- name: "missing protocol",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Protocol = ""
- },
- numErrs: 1,
- },
- {
- name: "invalid protocol",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Protocol = "INVALID"
- },
- numErrs: 1,
- },
- {
- name: "invalid cluster ip",
- tweakSvc: func(s *core.Service) {
- s.Spec.ClusterIP = "invalid"
- },
- numErrs: 1,
- },
- {
- name: "missing port",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Port = 0
- },
- numErrs: 1,
- },
- {
- name: "invalid port",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Port = 65536
- },
- numErrs: 1,
- },
- {
- name: "invalid TargetPort int",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].TargetPort = intstr.FromInt(65536)
- },
- numErrs: 1,
- },
- {
- name: "valid port headless",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Port = 11722
- s.Spec.Ports[0].TargetPort = intstr.FromInt(11722)
- s.Spec.ClusterIP = core.ClusterIPNone
- },
- numErrs: 0,
- },
- {
- name: "invalid port headless 1",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Port = 11722
- s.Spec.Ports[0].TargetPort = intstr.FromInt(11721)
- s.Spec.ClusterIP = core.ClusterIPNone
- },
- // in the v1 API, targetPorts on headless services were tolerated.
- // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
- // numErrs: 1,
- numErrs: 0,
- },
- {
- name: "invalid port headless 2",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Port = 11722
- s.Spec.Ports[0].TargetPort = intstr.FromString("target")
- s.Spec.ClusterIP = core.ClusterIPNone
- },
- // in the v1 API, targetPorts on headless services were tolerated.
- // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
- // numErrs: 1,
- numErrs: 0,
- },
- {
- name: "invalid publicIPs localhost",
- tweakSvc: func(s *core.Service) {
- s.Spec.ExternalIPs = []string{"127.0.0.1"}
- },
- numErrs: 1,
- },
- {
- name: "invalid publicIPs unspecified",
- tweakSvc: func(s *core.Service) {
- s.Spec.ExternalIPs = []string{"0.0.0.0"}
- },
- numErrs: 1,
- },
- {
- name: "invalid publicIPs loopback",
- tweakSvc: func(s *core.Service) {
- s.Spec.ExternalIPs = []string{"127.0.0.1"}
- },
- numErrs: 1,
- },
- {
- name: "invalid publicIPs host",
- tweakSvc: func(s *core.Service) {
- s.Spec.ExternalIPs = []string{"myhost.mydomain"}
- },
- numErrs: 1,
- },
- {
- name: "dup port name",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Name = "p"
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 1,
- },
- {
- name: "valid load balancer protocol UDP 1",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports[0].Protocol = "UDP"
- },
- numErrs: 0,
- },
- {
- name: "valid load balancer protocol UDP 2",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports[0] = core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(12345)}
- },
- numErrs: 0,
- },
- {
- name: "invalid load balancer with mix protocol",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 1,
- },
- {
- name: "valid 1",
- tweakSvc: func(s *core.Service) {
- // do nothing
- },
- numErrs: 0,
- },
- {
- name: "valid 2",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].Protocol = "UDP"
- s.Spec.Ports[0].TargetPort = intstr.FromInt(12345)
- },
- numErrs: 0,
- },
- {
- name: "valid 3",
- tweakSvc: func(s *core.Service) {
- s.Spec.Ports[0].TargetPort = intstr.FromString("http")
- },
- numErrs: 0,
- },
- {
- name: "valid cluster ip - none ",
- tweakSvc: func(s *core.Service) {
- s.Spec.ClusterIP = "None"
- },
- numErrs: 0,
- },
- {
- name: "valid cluster ip - empty",
- tweakSvc: func(s *core.Service) {
- s.Spec.ClusterIP = ""
- s.Spec.Ports[0].TargetPort = intstr.FromString("http")
- },
- numErrs: 0,
- },
- {
- name: "valid type - cluster",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- },
- numErrs: 0,
- },
- {
- name: "valid type - loadbalancer",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- },
- numErrs: 0,
- },
- {
- name: "valid type loadbalancer 2 ports",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 0,
- },
- {
- name: "valid external load balancer 2 ports",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 0,
- },
- {
- name: "duplicate nodeports",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)})
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(2)})
- },
- numErrs: 1,
- },
- {
- name: "duplicate nodeports (different protocols)",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)})
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt(2)})
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt(3)})
- },
- numErrs: 0,
- },
- {
- name: "invalid duplicate ports (with same protocol)",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(8080)})
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(80)})
- },
- numErrs: 1,
- },
- {
- name: "valid duplicate ports (with different protocols)",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(8080)})
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(80)})
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt(8088)})
- },
- numErrs: 0,
- },
- {
- name: "valid type - cluster",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- },
- numErrs: 0,
- },
- {
- name: "valid type - nodeport",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- },
- numErrs: 0,
- },
- {
- name: "valid type - loadbalancer",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- },
- numErrs: 0,
- },
- {
- name: "valid type loadbalancer 2 ports",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 0,
- },
- {
- name: "valid type loadbalancer with NodePort",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 0,
- },
- {
- name: "valid type=NodePort service with NodePort",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 0,
- },
- {
- name: "valid type=NodePort service without NodePort",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 0,
- },
- {
- name: "valid cluster service without NodePort",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 0,
- },
- {
- name: "invalid cluster service with NodePort",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", NodePort: 12345, TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 1,
- },
- {
- name: "invalid public service with duplicate NodePort",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p1", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)})
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "p2", Port: 2, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(2)})
- },
- numErrs: 1,
- },
- {
- name: "valid type=LoadBalancer",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 0,
- },
- {
- // For now we open firewalls, and its insecure if we open 10250, remove this
- // when we have better protections in place.
- name: "invalid port type=LoadBalancer",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: intstr.FromInt(12345)})
- },
- numErrs: 1,
- },
- {
- name: "valid LoadBalancer source range annotation",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/8, 5.6.7.8/16"
- },
- numErrs: 0,
- },
- {
- name: "empty LoadBalancer source range annotation",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = ""
- },
- numErrs: 0,
- },
- {
- name: "invalid LoadBalancer source range annotation (hostname)",
- tweakSvc: func(s *core.Service) {
- s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "foo.bar"
- },
- numErrs: 2,
- },
- {
- name: "invalid LoadBalancer source range annotation (invalid CIDR)",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Annotations[core.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33"
- },
- numErrs: 1,
- },
- {
- name: "invalid source range for non LoadBalancer type service",
- tweakSvc: func(s *core.Service) {
- s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"}
- },
- numErrs: 1,
- },
- {
- name: "valid LoadBalancer source range",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.LoadBalancerSourceRanges = []string{"1.2.3.4/8", "5.6.7.8/16"}
- },
- numErrs: 0,
- },
- {
- name: "empty LoadBalancer source range",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.LoadBalancerSourceRanges = []string{" "}
- },
- numErrs: 1,
- },
- {
- name: "invalid LoadBalancer source range",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.LoadBalancerSourceRanges = []string{"foo.bar"}
- },
- numErrs: 1,
- },
- {
- name: "valid ExternalName",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeExternalName
- s.Spec.ClusterIP = ""
- s.Spec.ExternalName = "foo.bar.example.com"
- },
- numErrs: 0,
- },
- {
- name: "valid ExternalName (trailing dot)",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeExternalName
- s.Spec.ClusterIP = ""
- s.Spec.ExternalName = "foo.bar.example.com."
- },
- numErrs: 0,
- },
- {
- name: "invalid ExternalName clusterIP (valid IP)",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeExternalName
- s.Spec.ClusterIP = "1.2.3.4"
- s.Spec.ExternalName = "foo.bar.example.com"
- },
- numErrs: 1,
- },
- {
- name: "invalid ExternalName clusterIP (None)",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeExternalName
- s.Spec.ClusterIP = "None"
- s.Spec.ExternalName = "foo.bar.example.com"
- },
- numErrs: 1,
- },
- {
- name: "invalid ExternalName (not a DNS name)",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeExternalName
- s.Spec.ClusterIP = ""
- s.Spec.ExternalName = "-123"
- },
- numErrs: 1,
- },
- {
- name: "LoadBalancer type cannot have None ClusterIP",
- tweakSvc: func(s *core.Service) {
- s.Spec.ClusterIP = "None"
- s.Spec.Type = core.ServiceTypeLoadBalancer
- },
- numErrs: 1,
- },
- {
- name: "invalid node port with clusterIP None",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)})
- s.Spec.ClusterIP = "None"
- },
- numErrs: 1,
- },
- // ESIPP section begins.
- {
- name: "invalid externalTraffic field",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.ExternalTrafficPolicy = "invalid"
- },
- numErrs: 1,
- },
- {
- name: "nagative healthCheckNodePort field",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal
- s.Spec.HealthCheckNodePort = -1
- },
- numErrs: 1,
- },
- {
- name: "nagative healthCheckNodePort field",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal
- s.Spec.HealthCheckNodePort = 31100
- },
- numErrs: 0,
- },
- // ESIPP section ends.
- {
- name: "invalid timeoutSeconds field",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- s.Spec.SessionAffinity = core.ServiceAffinityClientIP
- s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(-1),
- },
- }
- },
- numErrs: 1,
- },
- {
- name: "sessionAffinityConfig can't be set when session affinity is None",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.SessionAffinity = core.ServiceAffinityNone
- s.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(90),
- },
- }
- },
- numErrs: 1,
- },
- {
- name: "valid, nil service IPFamily",
- tweakSvc: func(s *core.Service) {
- s.Spec.IPFamily = nil
- },
- numErrs: 0,
- },
- {
- name: "valid, service with valid IPFamily",
- tweakSvc: func(s *core.Service) {
- ipv4Service := core.IPv4Protocol
- s.Spec.IPFamily = &ipv4Service
- },
- numErrs: 0,
- },
- {
- name: "invalid, service with invalid IPFamily",
- tweakSvc: func(s *core.Service) {
- invalidServiceIPFamily := core.IPFamily("not-a-valid-ip-family")
- s.Spec.IPFamily = &invalidServiceIPFamily
- },
- numErrs: 1,
- },
- {
- name: "valid topology keys",
- tweakSvc: func(s *core.Service) {
- s.Spec.TopologyKeys = []string{
- "kubernetes.io/hostname",
- "failure-domain.beta.kubernetes.io/zone",
- "failure-domain.beta.kubernetes.io/region",
- v1.TopologyKeyAny,
- }
- },
- numErrs: 0,
- },
- {
- name: "invalid topology key",
- tweakSvc: func(s *core.Service) {
- s.Spec.TopologyKeys = []string{"NoUppercaseOrSpecialCharsLike=Equals"}
- },
- numErrs: 1,
- },
- {
- name: "too many topology keys",
- tweakSvc: func(s *core.Service) {
- for i := 0; i < core.MaxServiceTopologyKeys+1; i++ {
- s.Spec.TopologyKeys = append(s.Spec.TopologyKeys, fmt.Sprintf("topologykey-%d", i))
- }
- },
- numErrs: 1,
- },
- {
- name: `"Any" was not the last key`,
- tweakSvc: func(s *core.Service) {
- s.Spec.TopologyKeys = []string{
- "kubernetes.io/hostname",
- v1.TopologyKeyAny,
- "failure-domain.beta.kubernetes.io/zone",
- }
- },
- numErrs: 1,
- },
- {
- name: `duplicate topology key`,
- tweakSvc: func(s *core.Service) {
- s.Spec.TopologyKeys = []string{
- "kubernetes.io/hostname",
- "kubernetes.io/hostname",
- "failure-domain.beta.kubernetes.io/zone",
- }
- },
- numErrs: 1,
- },
- {
- name: `use topology keys with externalTrafficPolicy=Local`,
- tweakSvc: func(s *core.Service) {
- s.Spec.ExternalTrafficPolicy = "Local"
- s.Spec.TopologyKeys = []string{
- "kubernetes.io/hostname",
- }
- },
- numErrs: 1,
- },
- }
- for _, tc := range testCases {
- svc := makeValidService()
- tc.tweakSvc(&svc)
- errs := ValidateService(&svc)
- if len(errs) != tc.numErrs {
- t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
- }
- }
- }
- func TestValidateServiceExternalTrafficFieldsCombination(t *testing.T) {
- testCases := []struct {
- name string
- tweakSvc func(svc *core.Service) // Given a basic valid service, each test case can customize it.
- numErrs int
- }{
- {
- name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal
- s.Spec.HealthCheckNodePort = 34567
- },
- numErrs: 0,
- },
- {
- name: "valid nodePort service with externalTrafficPolicy set",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal
- },
- numErrs: 0,
- },
- {
- name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- },
- numErrs: 0,
- },
- {
- name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeLoadBalancer
- s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeCluster
- s.Spec.HealthCheckNodePort = 34567
- },
- numErrs: 1,
- },
- {
- name: "cannot set healthCheckNodePort field on nodePort service",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeNodePort
- s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal
- s.Spec.HealthCheckNodePort = 34567
- },
- numErrs: 1,
- },
- {
- name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service",
- tweakSvc: func(s *core.Service) {
- s.Spec.Type = core.ServiceTypeClusterIP
- s.Spec.ExternalTrafficPolicy = core.ServiceExternalTrafficPolicyTypeLocal
- s.Spec.HealthCheckNodePort = 34567
- },
- numErrs: 2,
- },
- }
- for _, tc := range testCases {
- svc := makeValidService()
- tc.tweakSvc(&svc)
- errs := ValidateServiceExternalTrafficFieldsCombination(&svc)
- if len(errs) != tc.numErrs {
- t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
- }
- }
- }
- func TestValidateReplicationControllerStatus(t *testing.T) {
- tests := []struct {
- name string
- replicas int32
- fullyLabeledReplicas int32
- readyReplicas int32
- availableReplicas int32
- observedGeneration int64
- expectedErr bool
- }{
- {
- name: "valid status",
- replicas: 3,
- fullyLabeledReplicas: 3,
- readyReplicas: 2,
- availableReplicas: 1,
- observedGeneration: 2,
- expectedErr: false,
- },
- {
- name: "invalid replicas",
- replicas: -1,
- fullyLabeledReplicas: 3,
- readyReplicas: 2,
- availableReplicas: 1,
- observedGeneration: 2,
- expectedErr: true,
- },
- {
- name: "invalid fullyLabeledReplicas",
- replicas: 3,
- fullyLabeledReplicas: -1,
- readyReplicas: 2,
- availableReplicas: 1,
- observedGeneration: 2,
- expectedErr: true,
- },
- {
- name: "invalid readyReplicas",
- replicas: 3,
- fullyLabeledReplicas: 3,
- readyReplicas: -1,
- availableReplicas: 1,
- observedGeneration: 2,
- expectedErr: true,
- },
- {
- name: "invalid availableReplicas",
- replicas: 3,
- fullyLabeledReplicas: 3,
- readyReplicas: 3,
- availableReplicas: -1,
- observedGeneration: 2,
- expectedErr: true,
- },
- {
- name: "invalid observedGeneration",
- replicas: 3,
- fullyLabeledReplicas: 3,
- readyReplicas: 3,
- availableReplicas: 3,
- observedGeneration: -1,
- expectedErr: true,
- },
- {
- name: "fullyLabeledReplicas greater than replicas",
- replicas: 3,
- fullyLabeledReplicas: 4,
- readyReplicas: 3,
- availableReplicas: 3,
- observedGeneration: 1,
- expectedErr: true,
- },
- {
- name: "readyReplicas greater than replicas",
- replicas: 3,
- fullyLabeledReplicas: 3,
- readyReplicas: 4,
- availableReplicas: 3,
- observedGeneration: 1,
- expectedErr: true,
- },
- {
- name: "availableReplicas greater than replicas",
- replicas: 3,
- fullyLabeledReplicas: 3,
- readyReplicas: 3,
- availableReplicas: 4,
- observedGeneration: 1,
- expectedErr: true,
- },
- {
- name: "availableReplicas greater than readyReplicas",
- replicas: 3,
- fullyLabeledReplicas: 3,
- readyReplicas: 2,
- availableReplicas: 3,
- observedGeneration: 1,
- expectedErr: true,
- },
- }
- for _, test := range tests {
- status := core.ReplicationControllerStatus{
- Replicas: test.replicas,
- FullyLabeledReplicas: test.fullyLabeledReplicas,
- ReadyReplicas: test.readyReplicas,
- AvailableReplicas: test.availableReplicas,
- ObservedGeneration: test.observedGeneration,
- }
- if hasErr := len(ValidateReplicationControllerStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr {
- t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr)
- }
- }
- }
- func TestValidateReplicationControllerStatusUpdate(t *testing.T) {
- validSelector := map[string]string{"a": "b"}
- validPodTemplate := core.PodTemplate{
- Template: core.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: validSelector,
- },
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- }
- type rcUpdateTest struct {
- old core.ReplicationController
- update core.ReplicationController
- }
- successCases := []rcUpdateTest{
- {
- old: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- Status: core.ReplicationControllerStatus{
- Replicas: 2,
- },
- },
- update: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 3,
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- Status: core.ReplicationControllerStatus{
- Replicas: 4,
- },
- },
- },
- }
- for _, successCase := range successCases {
- successCase.old.ObjectMeta.ResourceVersion = "1"
- successCase.update.ObjectMeta.ResourceVersion = "1"
- if errs := ValidateReplicationControllerStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := map[string]rcUpdateTest{
- "negative replicas": {
- old: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- Status: core.ReplicationControllerStatus{
- Replicas: 3,
- },
- },
- update: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 2,
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- Status: core.ReplicationControllerStatus{
- Replicas: -3,
- },
- },
- },
- }
- for testName, errorCase := range errorCases {
- if errs := ValidateReplicationControllerStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
- t.Errorf("expected failure: %s", testName)
- }
- }
- }
- func TestValidateReplicationControllerUpdate(t *testing.T) {
- validSelector := map[string]string{"a": "b"}
- validPodTemplate := core.PodTemplate{
- Template: core.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: validSelector,
- },
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- }
- readWriteVolumePodTemplate := core.PodTemplate{
- Template: core.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: validSelector,
- },
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- Volumes: []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
- },
- },
- }
- invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
- invalidPodTemplate := core.PodTemplate{
- Template: core.PodTemplateSpec{
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- ObjectMeta: metav1.ObjectMeta{
- Labels: invalidSelector,
- },
- },
- }
- type rcUpdateTest struct {
- old core.ReplicationController
- update core.ReplicationController
- }
- successCases := []rcUpdateTest{
- {
- old: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- update: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 3,
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- },
- {
- old: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- update: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 1,
- Selector: validSelector,
- Template: &readWriteVolumePodTemplate.Template,
- },
- },
- },
- }
- for _, successCase := range successCases {
- successCase.old.ObjectMeta.ResourceVersion = "1"
- successCase.update.ObjectMeta.ResourceVersion = "1"
- if errs := ValidateReplicationControllerUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := map[string]rcUpdateTest{
- "more than one read/write": {
- old: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- update: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 2,
- Selector: validSelector,
- Template: &readWriteVolumePodTemplate.Template,
- },
- },
- },
- "invalid selector": {
- old: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- update: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 2,
- Selector: invalidSelector,
- Template: &validPodTemplate.Template,
- },
- },
- },
- "invalid pod": {
- old: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- update: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 2,
- Selector: validSelector,
- Template: &invalidPodTemplate.Template,
- },
- },
- },
- "negative replicas": {
- old: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- update: core.ReplicationController{
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: -1,
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- },
- }
- for testName, errorCase := range errorCases {
- if errs := ValidateReplicationControllerUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
- t.Errorf("expected failure: %s", testName)
- }
- }
- }
- func TestValidateReplicationController(t *testing.T) {
- validSelector := map[string]string{"a": "b"}
- validPodTemplate := core.PodTemplate{
- Template: core.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: validSelector,
- },
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- }
- readWriteVolumePodTemplate := core.PodTemplate{
- Template: core.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: validSelector,
- },
- Spec: core.PodSpec{
- Volumes: []core.Volume{{Name: "gcepd", VolumeSource: core.VolumeSource{GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- },
- }
- invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
- invalidPodTemplate := core.PodTemplate{
- Template: core.PodTemplateSpec{
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- ObjectMeta: metav1.ObjectMeta{
- Labels: invalidSelector,
- },
- },
- }
- successCases := []core.ReplicationController{
- {
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- {
- ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- {
- ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 1,
- Selector: validSelector,
- Template: &readWriteVolumePodTemplate.Template,
- },
- },
- }
- for _, successCase := range successCases {
- if errs := ValidateReplicationController(&successCase); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := map[string]core.ReplicationController{
- "zero-length ID": {
- ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- "missing-namespace": {
- ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- "empty selector": {
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Template: &validPodTemplate.Template,
- },
- },
- "selector_doesnt_match": {
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: map[string]string{"foo": "bar"},
- Template: &validPodTemplate.Template,
- },
- },
- "invalid manifest": {
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- },
- },
- "read-write persistent disk with > 1 pod": {
- ObjectMeta: metav1.ObjectMeta{Name: "abc"},
- Spec: core.ReplicationControllerSpec{
- Replicas: 2,
- Selector: validSelector,
- Template: &readWriteVolumePodTemplate.Template,
- },
- },
- "negative_replicas": {
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: -1,
- Selector: validSelector,
- },
- },
- "invalid_label": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc-123",
- Namespace: metav1.NamespaceDefault,
- Labels: map[string]string{
- "NoUppercaseOrSpecialCharsLike=Equals": "bar",
- },
- },
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- "invalid_label 2": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc-123",
- Namespace: metav1.NamespaceDefault,
- Labels: map[string]string{
- "NoUppercaseOrSpecialCharsLike=Equals": "bar",
- },
- },
- Spec: core.ReplicationControllerSpec{
- Template: &invalidPodTemplate.Template,
- },
- },
- "invalid_annotation": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc-123",
- Namespace: metav1.NamespaceDefault,
- Annotations: map[string]string{
- "NoUppercaseOrSpecialCharsLike=Equals": "bar",
- },
- },
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &validPodTemplate.Template,
- },
- },
- "invalid restart policy 1": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc-123",
- Namespace: metav1.NamespaceDefault,
- },
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &core.PodTemplateSpec{
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyOnFailure,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- ObjectMeta: metav1.ObjectMeta{
- Labels: validSelector,
- },
- },
- },
- },
- "invalid restart policy 2": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc-123",
- Namespace: metav1.NamespaceDefault,
- },
- Spec: core.ReplicationControllerSpec{
- Selector: validSelector,
- Template: &core.PodTemplateSpec{
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyNever,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- },
- ObjectMeta: metav1.ObjectMeta{
- Labels: validSelector,
- },
- },
- },
- },
- "template may not contain ephemeral containers": {
- ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
- Spec: core.ReplicationControllerSpec{
- Replicas: 1,
- Selector: validSelector,
- Template: &core.PodTemplateSpec{
- ObjectMeta: metav1.ObjectMeta{
- Labels: validSelector,
- },
- Spec: core.PodSpec{
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
- EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
- },
- },
- },
- },
- }
- for k, v := range errorCases {
- errs := ValidateReplicationController(&v)
- if len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- for i := range errs {
- field := errs[i].Field
- if !strings.HasPrefix(field, "spec.template.") &&
- field != "metadata.name" &&
- field != "metadata.namespace" &&
- field != "spec.selector" &&
- field != "spec.template" &&
- field != "GCEPersistentDisk.ReadOnly" &&
- field != "spec.replicas" &&
- field != "spec.template.labels" &&
- field != "metadata.annotations" &&
- field != "metadata.labels" &&
- field != "status.replicas" {
- t.Errorf("%s: missing prefix for: %v", k, errs[i])
- }
- }
- }
- }
- func TestValidateNode(t *testing.T) {
- opts := NodeValidationOptions{
- ValidateSingleHugePageResource: true,
- }
- validSelector := map[string]string{"a": "b"}
- invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
- successCases := []core.Node{
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Labels: validSelector,
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("my.org/gpu"): resource.MustParse("10"),
- core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("0"),
- },
- },
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "dedicated-node1",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- Spec: core.NodeSpec{
- // Add a valid taint to a node
- Taints: []core.Taint{{Key: "GPU", Value: "true", Effect: "NoSchedule"}},
- },
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Annotations: map[string]string{
- core.PreferAvoidPodsAnnotationKey: `
- {
- "preferAvoidPods": [
- {
- "podSignature": {
- "podController": {
- "apiVersion": "v1",
- "kind": "ReplicationController",
- "name": "foo",
- "uid": "abcdef123456",
- "controller": true
- }
- },
- "reason": "some reason",
- "message": "some message"
- }
- ]
- }`,
- },
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16"},
- },
- },
- }
- for _, successCase := range successCases {
- if errs := ValidateNode(&successCase, opts); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := map[string]core.Node{
- "zero-length Name": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "",
- Labels: validSelector,
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{},
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- },
- },
- },
- "invalid-labels": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc-123",
- Labels: invalidSelector,
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- },
- },
- },
- "missing-taint-key": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "dedicated-node1",
- },
- Spec: core.NodeSpec{
- // Add a taint with an empty key to a node
- Taints: []core.Taint{{Key: "", Value: "special-user-1", Effect: "NoSchedule"}},
- },
- },
- "bad-taint-key": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "dedicated-node1",
- },
- Spec: core.NodeSpec{
- // Add a taint with an invalid key to a node
- Taints: []core.Taint{{Key: "NoUppercaseOrSpecialCharsLike=Equals", Value: "special-user-1", Effect: "NoSchedule"}},
- },
- },
- "bad-taint-value": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "dedicated-node2",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- Spec: core.NodeSpec{
- // Add a taint with a bad value to a node
- Taints: []core.Taint{{Key: "dedicated", Value: "some\\bad\\value", Effect: "NoSchedule"}},
- },
- },
- "missing-taint-effect": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "dedicated-node3",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- Spec: core.NodeSpec{
- // Add a taint with an empty effect to a node
- Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: ""}},
- },
- },
- "invalid-taint-effect": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "dedicated-node3",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- Spec: core.NodeSpec{
- // Add a taint with NoExecute effect to a node
- Taints: []core.Taint{{Key: "dedicated", Value: "special-user-3", Effect: "NoScheduleNoAdmit"}},
- },
- },
- "duplicated-taints-with-same-key-effect": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "dedicated-node1",
- },
- Spec: core.NodeSpec{
- // Add two taints to the node with the same key and effect; should be rejected.
- Taints: []core.Taint{
- {Key: "dedicated", Value: "special-user-1", Effect: "NoSchedule"},
- {Key: "dedicated", Value: "special-user-2", Effect: "NoSchedule"},
- },
- },
- },
- "missing-podSignature": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc-123",
- Annotations: map[string]string{
- core.PreferAvoidPodsAnnotationKey: `
- {
- "preferAvoidPods": [
- {
- "reason": "some reason",
- "message": "some message"
- }
- ]
- }`,
- },
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{},
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- },
- "invalid-podController": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc-123",
- Annotations: map[string]string{
- core.PreferAvoidPodsAnnotationKey: `
- {
- "preferAvoidPods": [
- {
- "podSignature": {
- "podController": {
- "apiVersion": "v1",
- "kind": "ReplicationController",
- "name": "foo",
- "uid": "abcdef123456",
- "controller": false
- }
- },
- "reason": "some reason",
- "message": "some message"
- }
- ]
- }`,
- },
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{},
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- },
- "multiple-pre-allocated-hugepages": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Labels: validSelector,
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("my.org/gpu"): resource.MustParse("10"),
- core.ResourceName("hugepages-2Mi"): resource.MustParse("10Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("10Gi"),
- },
- },
- },
- "invalid-pod-cidr": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0"},
- },
- },
- "duplicate-pod-cidr": {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"10.0.0.1/16", "10.0.0.1/16"},
- },
- },
- }
- for k, v := range errorCases {
- errs := ValidateNode(&v, opts)
- if len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- for i := range errs {
- field := errs[i].Field
- expectedFields := map[string]bool{
- "metadata.name": true,
- "metadata.labels": true,
- "metadata.annotations": true,
- "metadata.namespace": true,
- "spec.externalID": true,
- "spec.taints[0].key": true,
- "spec.taints[0].value": true,
- "spec.taints[0].effect": true,
- "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature": true,
- "metadata.annotations.scheduler.alpha.kubernetes.io/preferAvoidPods[0].PodSignature.PodController.Controller": true,
- }
- if val, ok := expectedFields[field]; ok {
- if !val {
- t.Errorf("%s: missing prefix for: %v", k, errs[i])
- }
- }
- }
- }
- }
- func TestNodeValidationOptions(t *testing.T) {
- updateTests := []struct {
- oldNode core.Node
- node core.Node
- opts NodeValidationOptions
- valid bool
- }{
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "validate-single-hugepages",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("0"),
- },
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "validate-single-hugepages",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
- },
- },
- }, NodeValidationOptions{ValidateSingleHugePageResource: true}, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "validate-single-hugepages",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("1Gi"),
- },
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "validate-single-hugepages",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("1Gi"),
- },
- },
- }, NodeValidationOptions{ValidateSingleHugePageResource: true}, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "not-validate-single-hugepages",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("0"),
- },
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "not-validate-single-hugepages",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
- },
- },
- }, NodeValidationOptions{ValidateSingleHugePageResource: false}, true},
- }
- for i, test := range updateTests {
- test.oldNode.ObjectMeta.ResourceVersion = "1"
- test.node.ObjectMeta.ResourceVersion = "1"
- errs := ValidateNodeUpdate(&test.node, &test.oldNode, test.opts)
- if test.valid && len(errs) > 0 {
- t.Errorf("%d: Unexpected error: %v", i, errs)
- t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta)
- }
- if !test.valid && len(errs) == 0 {
- t.Errorf("%d: Unexpected non-error", i)
- t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta)
- }
- }
- nodeTests := []struct {
- node core.Node
- opts NodeValidationOptions
- valid bool
- }{
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "validate-single-hugepages",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
- },
- },
- }, NodeValidationOptions{ValidateSingleHugePageResource: true}, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "not-validate-single-hugepages",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName("hugepages-2Mi"): resource.MustParse("1Gi"),
- core.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
- core.ResourceName("hugepages-64Ki"): resource.MustParse("2Gi"),
- },
- },
- }, NodeValidationOptions{ValidateSingleHugePageResource: false}, true},
- }
- for i, test := range nodeTests {
- test.node.ObjectMeta.ResourceVersion = "1"
- errs := ValidateNode(&test.node, test.opts)
- if test.valid && len(errs) > 0 {
- t.Errorf("%d: Unexpected error: %v", i, errs)
- }
- if !test.valid && len(errs) == 0 {
- t.Errorf("%d: Unexpected non-error", i)
- }
- }
- }
- func TestValidateNodeUpdate(t *testing.T) {
- opts := NodeValidationOptions{
- ValidateSingleHugePageResource: true,
- }
- tests := []struct {
- oldNode core.Node
- node core.Node
- valid bool
- }{
- {core.Node{}, core.Node{}, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"}},
- core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "bar"},
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"foo": "bar"},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"foo": "baz"},
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"foo": "baz"},
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"bar": "foo"},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"foo": "baz"},
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16"},
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.123.0.0/16"},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16"},
- },
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceCPU: resource.MustParse("10000"),
- core.ResourceMemory: resource.MustParse("100"),
- },
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- core.ResourceMemory: resource.MustParse("10000"),
- },
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"bar": "foo"},
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceCPU: resource.MustParse("10000"),
- core.ResourceMemory: resource.MustParse("100"),
- },
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"bar": "fooobaz"},
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- core.ResourceMemory: resource.MustParse("10000"),
- },
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"bar": "foo"},
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "1.2.3.4"},
- },
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"bar": "fooobaz"},
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"foo": "baz"},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Labels: map[string]string{"Foo": "baz"},
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.NodeSpec{
- Unschedulable: false,
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.NodeSpec{
- Unschedulable: true,
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.NodeSpec{
- Unschedulable: false,
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "1.1.1.1"},
- {Type: core.NodeExternalIP, Address: "1.1.1.1"},
- },
- },
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Spec: core.NodeSpec{
- Unschedulable: false,
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "1.1.1.1"},
- {Type: core.NodeInternalIP, Address: "10.1.1.1"},
- },
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Annotations: map[string]string{
- core.PreferAvoidPodsAnnotationKey: `
- {
- "preferAvoidPods": [
- {
- "podSignature": {
- "podController": {
- "apiVersion": "v1",
- "kind": "ReplicationController",
- "name": "foo",
- "uid": "abcdef123456",
- "controller": true
- }
- },
- "reason": "some reason",
- "message": "some message"
- }
- ]
- }`,
- },
- },
- Spec: core.NodeSpec{
- Unschedulable: false,
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Annotations: map[string]string{
- core.PreferAvoidPodsAnnotationKey: `
- {
- "preferAvoidPods": [
- {
- "reason": "some reason",
- "message": "some message"
- }
- ]
- }`,
- },
- },
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Annotations: map[string]string{
- core.PreferAvoidPodsAnnotationKey: `
- {
- "preferAvoidPods": [
- {
- "podSignature": {
- "podController": {
- "apiVersion": "v1",
- "kind": "ReplicationController",
- "name": "foo",
- "uid": "abcdef123456",
- "controller": false
- }
- },
- "reason": "some reason",
- "message": "some message"
- }
- ]
- }`,
- },
- },
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "valid-extended-resources",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "valid-extended-resources",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("example.com/a"): resource.MustParse("5"),
- core.ResourceName("example.com/b"): resource.MustParse("10"),
- },
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "invalid-fractional-extended-capacity",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "invalid-fractional-extended-capacity",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("example.com/a"): resource.MustParse("500m"),
- },
- },
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "invalid-fractional-extended-allocatable",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "invalid-fractional-extended-allocatable",
- },
- Status: core.NodeStatus{
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("example.com/a"): resource.MustParse("5"),
- },
- Allocatable: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- core.ResourceName("example.com/a"): resource.MustParse("4.5"),
- },
- },
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "update-provider-id-when-not-set",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "update-provider-id-when-not-set",
- },
- Spec: core.NodeSpec{
- ProviderID: "provider:///new",
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "update-provider-id-when-set",
- },
- Spec: core.NodeSpec{
- ProviderID: "provider:///old",
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "update-provider-id-when-set",
- },
- Spec: core.NodeSpec{
- ProviderID: "provider:///new",
- },
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-cidrs-as-is",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16"},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-cidrs-as-is",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16"},
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-cidrs-as-is-2",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-cidrs-as-is-2",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
- },
- }, true},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-cidrs-not-same-length",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16", "192.167.0.0/16", "2000::/10"},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-cidrs-not-same-length",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
- },
- }, false},
- {core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-cidrs-not-same",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"192.168.0.0/16", "2000::/10"},
- },
- }, core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: "pod-cidrs-not-same",
- },
- Spec: core.NodeSpec{
- PodCIDRs: []string{"2000::/10", "192.168.0.0/16"},
- },
- }, false},
- }
- for i, test := range tests {
- test.oldNode.ObjectMeta.ResourceVersion = "1"
- test.node.ObjectMeta.ResourceVersion = "1"
- errs := ValidateNodeUpdate(&test.node, &test.oldNode, opts)
- if test.valid && len(errs) > 0 {
- t.Errorf("%d: Unexpected error: %v", i, errs)
- t.Logf("%#v vs %#v", test.oldNode.ObjectMeta, test.node.ObjectMeta)
- }
- if !test.valid && len(errs) == 0 {
- t.Errorf("%d: Unexpected non-error", i)
- }
- }
- }
- func TestValidateServiceUpdate(t *testing.T) {
- testCases := []struct {
- name string
- tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them
- numErrs int
- }{
- {
- name: "no change",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- // do nothing
- },
- numErrs: 0,
- },
- {
- name: "change name",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Name += "2"
- },
- numErrs: 1,
- },
- {
- name: "change namespace",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Namespace += "2"
- },
- numErrs: 1,
- },
- {
- name: "change label valid",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Labels["key"] = "other-value"
- },
- numErrs: 0,
- },
- {
- name: "add label",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Labels["key2"] = "value2"
- },
- numErrs: 0,
- },
- {
- name: "change cluster IP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "8.6.7.5"
- },
- numErrs: 1,
- },
- {
- name: "remove cluster IP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = ""
- },
- numErrs: 1,
- },
- {
- name: "change affinity",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Spec.SessionAffinity = "ClientIP"
- newSvc.Spec.SessionAffinityConfig = &core.SessionAffinityConfig{
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(90),
- },
- }
- },
- numErrs: 0,
- },
- {
- name: "remove affinity",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Spec.SessionAffinity = ""
- },
- numErrs: 1,
- },
- {
- name: "change type",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- },
- numErrs: 0,
- },
- {
- name: "remove type",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Spec.Type = ""
- },
- numErrs: 1,
- },
- {
- name: "change type -> nodeport",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Spec.Type = core.ServiceTypeNodePort
- },
- numErrs: 0,
- },
- {
- name: "add loadBalancerSourceRanges",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
- },
- numErrs: 0,
- },
- {
- name: "update loadBalancerSourceRanges",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
- oldSvc.Spec.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.LoadBalancerSourceRanges = []string{"10.100.0.0/16"}
- },
- numErrs: 0,
- },
- {
- name: "LoadBalancer type cannot have None ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- newSvc.Spec.ClusterIP = "None"
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- },
- numErrs: 1,
- },
- {
- name: "`None` ClusterIP cannot be changed",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.ClusterIP = "None"
- newSvc.Spec.ClusterIP = "1.2.3.4"
- },
- numErrs: 1,
- },
- {
- name: "`None` ClusterIP cannot be removed",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.ClusterIP = "None"
- newSvc.Spec.ClusterIP = ""
- },
- numErrs: 1,
- },
- {
- name: "Service with ClusterIP type cannot change its set ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeClusterIP
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with ClusterIP type can change its empty ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeClusterIP
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with ClusterIP type cannot change its set ClusterIP when changing type to NodePort",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeClusterIP
- newSvc.Spec.Type = core.ServiceTypeNodePort
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with ClusterIP type can change its empty ClusterIP when changing type to NodePort",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeClusterIP
- newSvc.Spec.Type = core.ServiceTypeNodePort
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with ClusterIP type cannot change its ClusterIP when changing type to LoadBalancer",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeClusterIP
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with ClusterIP type can change its empty ClusterIP when changing type to LoadBalancer",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeClusterIP
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with NodePort type cannot change its set ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeNodePort
- newSvc.Spec.Type = core.ServiceTypeNodePort
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with NodePort type can change its empty ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeNodePort
- newSvc.Spec.Type = core.ServiceTypeNodePort
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with NodePort type cannot change its set ClusterIP when changing type to ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeNodePort
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with NodePort type can change its empty ClusterIP when changing type to ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeNodePort
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with NodePort type cannot change its set ClusterIP when changing type to LoadBalancer",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeNodePort
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with NodePort type can change its empty ClusterIP when changing type to LoadBalancer",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeNodePort
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with LoadBalancer type cannot change its set ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with LoadBalancer type can change its empty ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.Type = core.ServiceTypeLoadBalancer
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with LoadBalancer type cannot change its set ClusterIP when changing type to NodePort",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.Type = core.ServiceTypeNodePort
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 1,
- },
- {
- name: "Service with LoadBalancer type can change its empty ClusterIP when changing type to NodePort",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeLoadBalancer
- newSvc.Spec.Type = core.ServiceTypeNodePort
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with ExternalName type can change its empty ClusterIP when changing type to ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeExternalName
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "Service with ExternalName type can change its set ClusterIP when changing type to ClusterIP",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeExternalName
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.ClusterIP = "1.2.3.4"
- newSvc.Spec.ClusterIP = "1.2.3.5"
- },
- numErrs: 0,
- },
- {
- name: "invalid node port with clusterIP None",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.Type = core.ServiceTypeNodePort
- newSvc.Spec.Type = core.ServiceTypeNodePort
- oldSvc.Spec.Ports = append(oldSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)})
- newSvc.Spec.Ports = append(newSvc.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)})
- oldSvc.Spec.ClusterIP = ""
- newSvc.Spec.ClusterIP = "None"
- },
- numErrs: 1,
- },
- /* Service IP Family */
- {
- name: "same ServiceIPFamily",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- ipv4Service := core.IPv4Protocol
- oldSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.IPFamily = &ipv4Service
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- newSvc.Spec.IPFamily = &ipv4Service
- },
- numErrs: 0,
- },
- {
- name: "ExternalName while changing Service IPFamily",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- ipv4Service := core.IPv4Protocol
- oldSvc.Spec.ExternalName = "somename"
- oldSvc.Spec.Type = core.ServiceTypeExternalName
- oldSvc.Spec.IPFamily = &ipv4Service
- ipv6Service := core.IPv6Protocol
- newSvc.Spec.ExternalName = "somename"
- newSvc.Spec.Type = core.ServiceTypeExternalName
- newSvc.Spec.IPFamily = &ipv6Service
- },
- numErrs: 0,
- },
- {
- name: "setting ipfamily from nil to v4",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.IPFamily = nil
- ipv4Service := core.IPv4Protocol
- newSvc.Spec.ExternalName = "somename"
- newSvc.Spec.IPFamily = &ipv4Service
- },
- numErrs: 0,
- },
- {
- name: "setting ipfamily from nil to v6",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- oldSvc.Spec.IPFamily = nil
- ipv6Service := core.IPv6Protocol
- newSvc.Spec.ExternalName = "somename"
- newSvc.Spec.IPFamily = &ipv6Service
- },
- numErrs: 0,
- },
- {
- name: "remove ipfamily",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- ipv6Service := core.IPv6Protocol
- oldSvc.Spec.IPFamily = &ipv6Service
- newSvc.Spec.IPFamily = nil
- },
- numErrs: 1,
- },
- {
- name: "change ServiceIPFamily",
- tweakSvc: func(oldSvc, newSvc *core.Service) {
- ipv4Service := core.IPv4Protocol
- oldSvc.Spec.Type = core.ServiceTypeClusterIP
- oldSvc.Spec.IPFamily = &ipv4Service
- ipv6Service := core.IPv6Protocol
- newSvc.Spec.Type = core.ServiceTypeClusterIP
- newSvc.Spec.IPFamily = &ipv6Service
- },
- numErrs: 1,
- },
- }
- for _, tc := range testCases {
- oldSvc := makeValidService()
- newSvc := makeValidService()
- tc.tweakSvc(&oldSvc, &newSvc)
- errs := ValidateServiceUpdate(&newSvc, &oldSvc)
- if len(errs) != tc.numErrs {
- t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
- }
- }
- }
- func TestValidateResourceNames(t *testing.T) {
- table := []struct {
- input string
- success bool
- expect string
- }{
- {"memory", true, ""},
- {"cpu", true, ""},
- {"storage", true, ""},
- {"requests.cpu", true, ""},
- {"requests.memory", true, ""},
- {"requests.storage", true, ""},
- {"limits.cpu", true, ""},
- {"limits.memory", true, ""},
- {"network", false, ""},
- {"disk", false, ""},
- {"", false, ""},
- {".", false, ""},
- {"..", false, ""},
- {"my.favorite.app.co/12345", true, ""},
- {"my.favorite.app.co/_12345", false, ""},
- {"my.favorite.app.co/12345_", false, ""},
- {"kubernetes.io/..", false, ""},
- {"kubernetes.io/" + strings.Repeat("a", 63), true, ""},
- {"kubernetes.io/" + strings.Repeat("a", 64), false, ""},
- {"kubernetes.io//", false, ""},
- {"kubernetes.io", false, ""},
- {"kubernetes.io/will/not/work/", false, ""},
- }
- for k, item := range table {
- err := validateResourceName(item.input, field.NewPath("field"))
- if len(err) != 0 && item.success {
- t.Errorf("expected no failure for input %q", item.input)
- } else if len(err) == 0 && !item.success {
- t.Errorf("expected failure for input %q", item.input)
- for i := range err {
- detail := err[i].Detail
- if detail != "" && !strings.Contains(detail, item.expect) {
- t.Errorf("%d: expected error detail either empty or %s, got %s", k, item.expect, detail)
- }
- }
- }
- }
- }
- func getResourceList(cpu, memory string) core.ResourceList {
- res := core.ResourceList{}
- if cpu != "" {
- res[core.ResourceCPU] = resource.MustParse(cpu)
- }
- if memory != "" {
- res[core.ResourceMemory] = resource.MustParse(memory)
- }
- return res
- }
- func getStorageResourceList(storage string) core.ResourceList {
- res := core.ResourceList{}
- if storage != "" {
- res[core.ResourceStorage] = resource.MustParse(storage)
- }
- return res
- }
- func getLocalStorageResourceList(ephemeralStorage string) core.ResourceList {
- res := core.ResourceList{}
- if ephemeralStorage != "" {
- res[core.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage)
- }
- return res
- }
- func TestValidateLimitRangeForLocalStorage(t *testing.T) {
- testCases := []struct {
- name string
- spec core.LimitRangeSpec
- }{
- {
- name: "all-fields-valid",
- spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePod,
- Max: getLocalStorageResourceList("10000Mi"),
- Min: getLocalStorageResourceList("100Mi"),
- MaxLimitRequestRatio: getLocalStorageResourceList(""),
- },
- {
- Type: core.LimitTypeContainer,
- Max: getLocalStorageResourceList("10000Mi"),
- Min: getLocalStorageResourceList("100Mi"),
- Default: getLocalStorageResourceList("500Mi"),
- DefaultRequest: getLocalStorageResourceList("200Mi"),
- MaxLimitRequestRatio: getLocalStorageResourceList(""),
- },
- },
- },
- },
- }
- for _, testCase := range testCases {
- limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: testCase.name, Namespace: "foo"}, Spec: testCase.spec}
- if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
- t.Errorf("Case %v, unexpected error: %v", testCase.name, errs)
- }
- }
- }
- func TestValidateLimitRange(t *testing.T) {
- successCases := []struct {
- name string
- spec core.LimitRangeSpec
- }{
- {
- name: "all-fields-valid",
- spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePod,
- Max: getResourceList("100m", "10000Mi"),
- Min: getResourceList("5m", "100Mi"),
- MaxLimitRequestRatio: getResourceList("10", ""),
- },
- {
- Type: core.LimitTypeContainer,
- Max: getResourceList("100m", "10000Mi"),
- Min: getResourceList("5m", "100Mi"),
- Default: getResourceList("50m", "500Mi"),
- DefaultRequest: getResourceList("10m", "200Mi"),
- MaxLimitRequestRatio: getResourceList("10", ""),
- },
- {
- Type: core.LimitTypePersistentVolumeClaim,
- Max: getStorageResourceList("10Gi"),
- Min: getStorageResourceList("5Gi"),
- },
- },
- },
- },
- {
- name: "pvc-min-only",
- spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePersistentVolumeClaim,
- Min: getStorageResourceList("5Gi"),
- },
- },
- },
- },
- {
- name: "pvc-max-only",
- spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePersistentVolumeClaim,
- Max: getStorageResourceList("10Gi"),
- },
- },
- },
- },
- {
- name: "all-fields-valid-big-numbers",
- spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypeContainer,
- Max: getResourceList("100m", "10000T"),
- Min: getResourceList("5m", "100Mi"),
- Default: getResourceList("50m", "500Mi"),
- DefaultRequest: getResourceList("10m", "200Mi"),
- MaxLimitRequestRatio: getResourceList("10", ""),
- },
- },
- },
- },
- {
- name: "thirdparty-fields-all-valid-standard-container-resources",
- spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: "thirdparty.com/foo",
- Max: getResourceList("100m", "10000T"),
- Min: getResourceList("5m", "100Mi"),
- Default: getResourceList("50m", "500Mi"),
- DefaultRequest: getResourceList("10m", "200Mi"),
- MaxLimitRequestRatio: getResourceList("10", ""),
- },
- },
- },
- },
- {
- name: "thirdparty-fields-all-valid-storage-resources",
- spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: "thirdparty.com/foo",
- Max: getStorageResourceList("10000T"),
- Min: getStorageResourceList("100Mi"),
- Default: getStorageResourceList("500Mi"),
- DefaultRequest: getStorageResourceList("200Mi"),
- MaxLimitRequestRatio: getStorageResourceList(""),
- },
- },
- },
- },
- }
- for _, successCase := range successCases {
- limitRange := &core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: successCase.name, Namespace: "foo"}, Spec: successCase.spec}
- if errs := ValidateLimitRange(limitRange); len(errs) != 0 {
- t.Errorf("Case %v, unexpected error: %v", successCase.name, errs)
- }
- }
- errorCases := map[string]struct {
- R core.LimitRange
- D string
- }{
- "zero-length-name": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
- "name or generateName is required",
- },
- "zero-length-namespace": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: core.LimitRangeSpec{}},
- "",
- },
- "invalid-name": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: core.LimitRangeSpec{}},
- dnsSubdomainLabelErrMsg,
- },
- "invalid-namespace": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: core.LimitRangeSpec{}},
- dnsLabelErrMsg,
- },
- "duplicate-limit-type": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePod,
- Max: getResourceList("100m", "10000m"),
- Min: getResourceList("0m", "100m"),
- },
- {
- Type: core.LimitTypePod,
- Min: getResourceList("0m", "100m"),
- },
- },
- }},
- "",
- },
- "default-limit-type-pod": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePod,
- Max: getResourceList("100m", "10000m"),
- Min: getResourceList("0m", "100m"),
- Default: getResourceList("10m", "100m"),
- },
- },
- }},
- "may not be specified when `type` is 'Pod'",
- },
- "default-request-limit-type-pod": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePod,
- Max: getResourceList("100m", "10000m"),
- Min: getResourceList("0m", "100m"),
- DefaultRequest: getResourceList("10m", "100m"),
- },
- },
- }},
- "may not be specified when `type` is 'Pod'",
- },
- "min value 100m is greater than max value 10m": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePod,
- Max: getResourceList("10m", ""),
- Min: getResourceList("100m", ""),
- },
- },
- }},
- "min value 100m is greater than max value 10m",
- },
- "invalid spec default outside range": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypeContainer,
- Max: getResourceList("1", ""),
- Min: getResourceList("100m", ""),
- Default: getResourceList("2000m", ""),
- },
- },
- }},
- "default value 2 is greater than max value 1",
- },
- "invalid spec default request outside range": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypeContainer,
- Max: getResourceList("1", ""),
- Min: getResourceList("100m", ""),
- DefaultRequest: getResourceList("2000m", ""),
- },
- },
- }},
- "default request value 2 is greater than max value 1",
- },
- "invalid spec default request more than default": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypeContainer,
- Max: getResourceList("2", ""),
- Min: getResourceList("100m", ""),
- Default: getResourceList("500m", ""),
- DefaultRequest: getResourceList("800m", ""),
- },
- },
- }},
- "default request value 800m is greater than default limit value 500m",
- },
- "invalid spec maxLimitRequestRatio less than 1": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePod,
- MaxLimitRequestRatio: getResourceList("800m", ""),
- },
- },
- }},
- "ratio 800m is less than 1",
- },
- "invalid spec maxLimitRequestRatio greater than max/min": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypeContainer,
- Max: getResourceList("", "2Gi"),
- Min: getResourceList("", "512Mi"),
- MaxLimitRequestRatio: getResourceList("", "10"),
- },
- },
- }},
- "ratio 10 is greater than max/min = 4.000000",
- },
- "invalid non standard limit type": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: "foo",
- Max: getStorageResourceList("10000T"),
- Min: getStorageResourceList("100Mi"),
- Default: getStorageResourceList("500Mi"),
- DefaultRequest: getStorageResourceList("200Mi"),
- MaxLimitRequestRatio: getStorageResourceList(""),
- },
- },
- }},
- "must be a standard limit type or fully qualified",
- },
- "min and max values missing, one required": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePersistentVolumeClaim,
- },
- },
- }},
- "either minimum or maximum storage value is required, but neither was provided",
- },
- "invalid min greater than max": {
- core.LimitRange{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: core.LimitRangeSpec{
- Limits: []core.LimitRangeItem{
- {
- Type: core.LimitTypePersistentVolumeClaim,
- Min: getStorageResourceList("10Gi"),
- Max: getStorageResourceList("1Gi"),
- },
- },
- }},
- "min value 10Gi is greater than max value 1Gi",
- },
- }
- for k, v := range errorCases {
- errs := ValidateLimitRange(&v.R)
- if len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- for i := range errs {
- detail := errs[i].Detail
- if !strings.Contains(detail, v.D) {
- t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail)
- }
- }
- }
- }
- func TestValidatePersistentVolumeClaimStatusUpdate(t *testing.T) {
- validClaim := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- })
- validConditionUpdate := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadWriteOnce,
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- }, core.PersistentVolumeClaimStatus{
- Phase: core.ClaimPending,
- Conditions: []core.PersistentVolumeClaimCondition{
- {Type: core.PersistentVolumeClaimResizing, Status: core.ConditionTrue},
- },
- })
- scenarios := map[string]struct {
- isExpectedFailure bool
- oldClaim *core.PersistentVolumeClaim
- newClaim *core.PersistentVolumeClaim
- enableResize bool
- }{
- "condition-update-with-enabled-feature-gate": {
- isExpectedFailure: false,
- oldClaim: validClaim,
- newClaim: validConditionUpdate,
- enableResize: true,
- },
- }
- for name, scenario := range scenarios {
- t.Run(name, func(t *testing.T) {
- // ensure we have a resource version specified for updates
- scenario.oldClaim.ResourceVersion = "1"
- scenario.newClaim.ResourceVersion = "1"
- errs := ValidatePersistentVolumeClaimStatusUpdate(scenario.newClaim, scenario.oldClaim)
- if len(errs) == 0 && scenario.isExpectedFailure {
- t.Errorf("Unexpected success for scenario: %s", name)
- }
- if len(errs) > 0 && !scenario.isExpectedFailure {
- t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
- }
- })
- }
- }
- func TestValidateResourceQuota(t *testing.T) {
- spec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- core.ResourceMemory: resource.MustParse("10000"),
- core.ResourceRequestsCPU: resource.MustParse("100"),
- core.ResourceRequestsMemory: resource.MustParse("10000"),
- core.ResourceLimitsCPU: resource.MustParse("100"),
- core.ResourceLimitsMemory: resource.MustParse("10000"),
- core.ResourcePods: resource.MustParse("10"),
- core.ResourceServices: resource.MustParse("0"),
- core.ResourceReplicationControllers: resource.MustParse("10"),
- core.ResourceQuotas: resource.MustParse("10"),
- core.ResourceConfigMaps: resource.MustParse("10"),
- core.ResourceSecrets: resource.MustParse("10"),
- },
- }
- terminatingSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- core.ResourceLimitsCPU: resource.MustParse("200"),
- },
- Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating},
- }
- nonTerminatingSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- },
- Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotTerminating},
- }
- bestEffortSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourcePods: resource.MustParse("100"),
- },
- Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort},
- }
- nonBestEffortSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- },
- Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotBestEffort},
- }
- scopeSelectorSpec := core.ResourceQuotaSpec{
- ScopeSelector: &core.ScopeSelector{
- MatchExpressions: []core.ScopedResourceSelectorRequirement{
- {
- ScopeName: core.ResourceQuotaScopePriorityClass,
- Operator: core.ScopeSelectorOpIn,
- Values: []string{"cluster-services"},
- },
- },
- },
- }
- // storage is not yet supported as a quota tracked resource
- invalidQuotaResourceSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceStorage: resource.MustParse("10"),
- },
- }
- negativeSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("-100"),
- core.ResourceMemory: resource.MustParse("-10000"),
- core.ResourcePods: resource.MustParse("-10"),
- core.ResourceServices: resource.MustParse("-10"),
- core.ResourceReplicationControllers: resource.MustParse("-10"),
- core.ResourceQuotas: resource.MustParse("-10"),
- core.ResourceConfigMaps: resource.MustParse("-10"),
- core.ResourceSecrets: resource.MustParse("-10"),
- },
- }
- fractionalComputeSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100m"),
- },
- }
- fractionalPodSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourcePods: resource.MustParse(".1"),
- core.ResourceServices: resource.MustParse(".5"),
- core.ResourceReplicationControllers: resource.MustParse("1.25"),
- core.ResourceQuotas: resource.MustParse("2.5"),
- },
- }
- invalidTerminatingScopePairsSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- },
- Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating},
- }
- invalidBestEffortScopePairsSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourcePods: resource.MustParse("100"),
- },
- Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort},
- }
- invalidScopeNameSpec := core.ResourceQuotaSpec{
- Hard: core.ResourceList{
- core.ResourceCPU: resource.MustParse("100"),
- },
- Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScope("foo")},
- }
- successCases := []core.ResourceQuota{
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "foo",
- },
- Spec: spec,
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "foo",
- },
- Spec: fractionalComputeSpec,
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "foo",
- },
- Spec: terminatingSpec,
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "foo",
- },
- Spec: nonTerminatingSpec,
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "foo",
- },
- Spec: bestEffortSpec,
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "foo",
- },
- Spec: scopeSelectorSpec,
- },
- {
- ObjectMeta: metav1.ObjectMeta{
- Name: "abc",
- Namespace: "foo",
- },
- Spec: nonBestEffortSpec,
- },
- }
- for _, successCase := range successCases {
- if errs := ValidateResourceQuota(&successCase); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := map[string]struct {
- R core.ResourceQuota
- D string
- }{
- "zero-length Name": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec},
- "name or generateName is required",
- },
- "zero-length Namespace": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec},
- "",
- },
- "invalid Name": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec},
- dnsSubdomainLabelErrMsg,
- },
- "invalid Namespace": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec},
- dnsLabelErrMsg,
- },
- "negative-limits": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec},
- isNegativeErrorMsg,
- },
- "fractional-api-resource": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec},
- isNotIntegerErrorMsg,
- },
- "invalid-quota-resource": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec},
- isInvalidQuotaResource,
- },
- "invalid-quota-terminating-pair": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec},
- "conflicting scopes",
- },
- "invalid-quota-besteffort-pair": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec},
- "conflicting scopes",
- },
- "invalid-quota-scope-name": {
- core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec},
- "unsupported scope",
- },
- }
- for k, v := range errorCases {
- errs := ValidateResourceQuota(&v.R)
- if len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- for i := range errs {
- if !strings.Contains(errs[i].Detail, v.D) {
- t.Errorf("[%s]: expected error detail either empty or %s, got %s", k, v.D, errs[i].Detail)
- }
- }
- }
- }
- func TestValidateNamespace(t *testing.T) {
- validLabels := map[string]string{"a": "b"}
- invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
- successCases := []core.Namespace{
- {
- ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: validLabels},
- },
- {
- ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
- Spec: core.NamespaceSpec{
- Finalizers: []core.FinalizerName{"example.com/something", "example.com/other"},
- },
- },
- }
- for _, successCase := range successCases {
- if errs := ValidateNamespace(&successCase); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- errorCases := map[string]struct {
- R core.Namespace
- D string
- }{
- "zero-length name": {
- core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ""}},
- "",
- },
- "defined-namespace": {
- core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}},
- "",
- },
- "invalid-labels": {
- core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "abc", Labels: invalidLabels}},
- "",
- },
- }
- for k, v := range errorCases {
- errs := ValidateNamespace(&v.R)
- if len(errs) == 0 {
- t.Errorf("expected failure for %s", k)
- }
- }
- }
- func TestValidateNamespaceFinalizeUpdate(t *testing.T) {
- tests := []struct {
- oldNamespace core.Namespace
- namespace core.Namespace
- valid bool
- }{
- {core.Namespace{}, core.Namespace{}, true},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"}},
- core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"},
- Spec: core.NamespaceSpec{
- Finalizers: []core.FinalizerName{"Foo"},
- },
- }, false},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"},
- Spec: core.NamespaceSpec{
- Finalizers: []core.FinalizerName{"foo.com/bar"},
- },
- },
- core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"},
- Spec: core.NamespaceSpec{
- Finalizers: []core.FinalizerName{"foo.com/bar", "what.com/bar"},
- },
- }, true},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "fooemptyfinalizer"},
- Spec: core.NamespaceSpec{
- Finalizers: []core.FinalizerName{"foo.com/bar"},
- },
- },
- core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "fooemptyfinalizer"},
- Spec: core.NamespaceSpec{
- Finalizers: []core.FinalizerName{"", "foo.com/bar", "what.com/bar"},
- },
- }, false},
- }
- for i, test := range tests {
- test.namespace.ObjectMeta.ResourceVersion = "1"
- test.oldNamespace.ObjectMeta.ResourceVersion = "1"
- errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace)
- if test.valid && len(errs) > 0 {
- t.Errorf("%d: Unexpected error: %v", i, errs)
- t.Logf("%#v vs %#v", test.oldNamespace, test.namespace)
- }
- if !test.valid && len(errs) == 0 {
- t.Errorf("%d: Unexpected non-error", i)
- }
- }
- }
- func TestValidateNamespaceStatusUpdate(t *testing.T) {
- now := metav1.Now()
- tests := []struct {
- oldNamespace core.Namespace
- namespace core.Namespace
- valid bool
- }{
- {core.Namespace{}, core.Namespace{
- Status: core.NamespaceStatus{
- Phase: core.NamespaceActive,
- },
- }, true},
- // Cannot set deletionTimestamp via status update
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"}},
- core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- DeletionTimestamp: &now},
- Status: core.NamespaceStatus{
- Phase: core.NamespaceTerminating,
- },
- }, false},
- // Can update phase via status update
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- DeletionTimestamp: &now}},
- core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- DeletionTimestamp: &now},
- Status: core.NamespaceStatus{
- Phase: core.NamespaceTerminating,
- },
- }, true},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"}},
- core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"},
- Status: core.NamespaceStatus{
- Phase: core.NamespaceTerminating,
- },
- }, false},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo"}},
- core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "bar"},
- Status: core.NamespaceStatus{
- Phase: core.NamespaceTerminating,
- },
- }, false},
- }
- for i, test := range tests {
- test.namespace.ObjectMeta.ResourceVersion = "1"
- test.oldNamespace.ObjectMeta.ResourceVersion = "1"
- errs := ValidateNamespaceStatusUpdate(&test.namespace, &test.oldNamespace)
- if test.valid && len(errs) > 0 {
- t.Errorf("%d: Unexpected error: %v", i, errs)
- t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
- }
- if !test.valid && len(errs) == 0 {
- t.Errorf("%d: Unexpected non-error", i)
- }
- }
- }
- func TestValidateNamespaceUpdate(t *testing.T) {
- tests := []struct {
- oldNamespace core.Namespace
- namespace core.Namespace
- valid bool
- }{
- {core.Namespace{}, core.Namespace{}, true},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo1"}},
- core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "bar1"},
- }, false},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo2",
- Labels: map[string]string{"foo": "bar"},
- },
- }, core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo2",
- Labels: map[string]string{"foo": "baz"},
- },
- }, true},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo3",
- },
- }, core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo3",
- Labels: map[string]string{"foo": "baz"},
- },
- }, true},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo4",
- Labels: map[string]string{"bar": "foo"},
- },
- }, core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo4",
- Labels: map[string]string{"foo": "baz"},
- },
- }, true},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo5",
- Labels: map[string]string{"foo": "baz"},
- },
- }, core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo5",
- Labels: map[string]string{"Foo": "baz"},
- },
- }, true},
- {core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo6",
- Labels: map[string]string{"foo": "baz"},
- },
- }, core.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo6",
- Labels: map[string]string{"Foo": "baz"},
- },
- Spec: core.NamespaceSpec{
- Finalizers: []core.FinalizerName{"kubernetes"},
- },
- Status: core.NamespaceStatus{
- Phase: core.NamespaceTerminating,
- },
- }, true},
- }
- for i, test := range tests {
- test.namespace.ObjectMeta.ResourceVersion = "1"
- test.oldNamespace.ObjectMeta.ResourceVersion = "1"
- errs := ValidateNamespaceUpdate(&test.namespace, &test.oldNamespace)
- if test.valid && len(errs) > 0 {
- t.Errorf("%d: Unexpected error: %v", i, errs)
- t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
- }
- if !test.valid && len(errs) == 0 {
- t.Errorf("%d: Unexpected non-error", i)
- }
- }
- }
- func TestValidateSecret(t *testing.T) {
- // Opaque secret validation
- validSecret := func() core.Secret {
- return core.Secret{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
- Data: map[string][]byte{
- "data-1": []byte("bar"),
- },
- }
- }
- var (
- emptyName = validSecret()
- invalidName = validSecret()
- emptyNs = validSecret()
- invalidNs = validSecret()
- overMaxSize = validSecret()
- invalidKey = validSecret()
- leadingDotKey = validSecret()
- dotKey = validSecret()
- doubleDotKey = validSecret()
- )
- emptyName.Name = ""
- invalidName.Name = "NoUppercaseOrSpecialCharsLike=Equals"
- emptyNs.Namespace = ""
- invalidNs.Namespace = "NoUppercaseOrSpecialCharsLike=Equals"
- overMaxSize.Data = map[string][]byte{
- "over": make([]byte, core.MaxSecretSize+1),
- }
- invalidKey.Data["a*b"] = []byte("whoops")
- leadingDotKey.Data[".key"] = []byte("bar")
- dotKey.Data["."] = []byte("bar")
- doubleDotKey.Data[".."] = []byte("bar")
- // kubernetes.io/service-account-token secret validation
- validServiceAccountTokenSecret := func() core.Secret {
- return core.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "bar",
- Annotations: map[string]string{
- core.ServiceAccountNameKey: "foo",
- },
- },
- Type: core.SecretTypeServiceAccountToken,
- Data: map[string][]byte{
- "data-1": []byte("bar"),
- },
- }
- }
- var (
- emptyTokenAnnotation = validServiceAccountTokenSecret()
- missingTokenAnnotation = validServiceAccountTokenSecret()
- missingTokenAnnotations = validServiceAccountTokenSecret()
- )
- emptyTokenAnnotation.Annotations[core.ServiceAccountNameKey] = ""
- delete(missingTokenAnnotation.Annotations, core.ServiceAccountNameKey)
- missingTokenAnnotations.Annotations = nil
- tests := map[string]struct {
- secret core.Secret
- valid bool
- }{
- "valid": {validSecret(), true},
- "empty name": {emptyName, false},
- "invalid name": {invalidName, false},
- "empty namespace": {emptyNs, false},
- "invalid namespace": {invalidNs, false},
- "over max size": {overMaxSize, false},
- "invalid key": {invalidKey, false},
- "valid service-account-token secret": {validServiceAccountTokenSecret(), true},
- "empty service-account-token annotation": {emptyTokenAnnotation, false},
- "missing service-account-token annotation": {missingTokenAnnotation, false},
- "missing service-account-token annotations": {missingTokenAnnotations, false},
- "leading dot key": {leadingDotKey, true},
- "dot key": {dotKey, false},
- "double dot key": {doubleDotKey, false},
- }
- for name, tc := range tests {
- errs := ValidateSecret(&tc.secret)
- if tc.valid && len(errs) > 0 {
- t.Errorf("%v: Unexpected error: %v", name, errs)
- }
- if !tc.valid && len(errs) == 0 {
- t.Errorf("%v: Unexpected non-error", name)
- }
- }
- }
- func TestValidateSecretUpdate(t *testing.T) {
- validSecret := func() core.Secret {
- return core.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: "bar",
- ResourceVersion: "20",
- },
- Data: map[string][]byte{
- "data-1": []byte("bar"),
- },
- }
- }
- falseVal := false
- trueVal := true
- secret := validSecret()
- immutableSecret := validSecret()
- immutableSecret.Immutable = &trueVal
- mutableSecret := validSecret()
- mutableSecret.Immutable = &falseVal
- secretWithData := validSecret()
- secretWithData.Data["data-2"] = []byte("baz")
- immutableSecretWithData := validSecret()
- immutableSecretWithData.Immutable = &trueVal
- immutableSecretWithData.Data["data-2"] = []byte("baz")
- secretWithChangedData := validSecret()
- secretWithChangedData.Data["data-1"] = []byte("foo")
- immutableSecretWithChangedData := validSecret()
- immutableSecretWithChangedData.Immutable = &trueVal
- immutableSecretWithChangedData.Data["data-1"] = []byte("foo")
- tests := []struct {
- name string
- oldSecret core.Secret
- newSecret core.Secret
- valid bool
- }{
- {
- name: "mark secret immutable",
- oldSecret: secret,
- newSecret: immutableSecret,
- valid: true,
- },
- {
- name: "revert immutable secret",
- oldSecret: immutableSecret,
- newSecret: secret,
- valid: false,
- },
- {
- name: "makr immutable secret mutable",
- oldSecret: immutableSecret,
- newSecret: mutableSecret,
- valid: false,
- },
- {
- name: "add data in secret",
- oldSecret: secret,
- newSecret: secretWithData,
- valid: true,
- },
- {
- name: "add data in immutable secret",
- oldSecret: immutableSecret,
- newSecret: immutableSecretWithData,
- valid: false,
- },
- {
- name: "change data in secret",
- oldSecret: secret,
- newSecret: secretWithChangedData,
- valid: true,
- },
- {
- name: "change data in immutable secret",
- oldSecret: immutableSecret,
- newSecret: immutableSecretWithChangedData,
- valid: false,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret)
- if tc.valid && len(errs) > 0 {
- t.Errorf("Unexpected error: %v", errs)
- }
- if !tc.valid && len(errs) == 0 {
- t.Errorf("Unexpected lack of error")
- }
- })
- }
- }
- func TestValidateDockerConfigSecret(t *testing.T) {
- validDockerSecret := func() core.Secret {
- return core.Secret{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
- Type: core.SecretTypeDockercfg,
- Data: map[string][]byte{
- core.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`),
- },
- }
- }
- validDockerSecret2 := func() core.Secret {
- return core.Secret{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
- Type: core.SecretTypeDockerConfigJSON,
- Data: map[string][]byte{
- core.DockerConfigJSONKey: []byte(`{"auths":{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}}`),
- },
- }
- }
- var (
- missingDockerConfigKey = validDockerSecret()
- emptyDockerConfigKey = validDockerSecret()
- invalidDockerConfigKey = validDockerSecret()
- missingDockerConfigKey2 = validDockerSecret2()
- emptyDockerConfigKey2 = validDockerSecret2()
- invalidDockerConfigKey2 = validDockerSecret2()
- )
- delete(missingDockerConfigKey.Data, core.DockerConfigKey)
- emptyDockerConfigKey.Data[core.DockerConfigKey] = []byte("")
- invalidDockerConfigKey.Data[core.DockerConfigKey] = []byte("bad")
- delete(missingDockerConfigKey2.Data, core.DockerConfigJSONKey)
- emptyDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("")
- invalidDockerConfigKey2.Data[core.DockerConfigJSONKey] = []byte("bad")
- tests := map[string]struct {
- secret core.Secret
- valid bool
- }{
- "valid dockercfg": {validDockerSecret(), true},
- "missing dockercfg": {missingDockerConfigKey, false},
- "empty dockercfg": {emptyDockerConfigKey, false},
- "invalid dockercfg": {invalidDockerConfigKey, false},
- "valid config.json": {validDockerSecret2(), true},
- "missing config.json": {missingDockerConfigKey2, false},
- "empty config.json": {emptyDockerConfigKey2, false},
- "invalid config.json": {invalidDockerConfigKey2, false},
- }
- for name, tc := range tests {
- errs := ValidateSecret(&tc.secret)
- if tc.valid && len(errs) > 0 {
- t.Errorf("%v: Unexpected error: %v", name, errs)
- }
- if !tc.valid && len(errs) == 0 {
- t.Errorf("%v: Unexpected non-error", name)
- }
- }
- }
- func TestValidateBasicAuthSecret(t *testing.T) {
- validBasicAuthSecret := func() core.Secret {
- return core.Secret{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
- Type: core.SecretTypeBasicAuth,
- Data: map[string][]byte{
- core.BasicAuthUsernameKey: []byte("username"),
- core.BasicAuthPasswordKey: []byte("password"),
- },
- }
- }
- var (
- missingBasicAuthUsernamePasswordKeys = validBasicAuthSecret()
- )
- delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthUsernameKey)
- delete(missingBasicAuthUsernamePasswordKeys.Data, core.BasicAuthPasswordKey)
- tests := map[string]struct {
- secret core.Secret
- valid bool
- }{
- "valid": {validBasicAuthSecret(), true},
- "missing username and password": {missingBasicAuthUsernamePasswordKeys, false},
- }
- for name, tc := range tests {
- errs := ValidateSecret(&tc.secret)
- if tc.valid && len(errs) > 0 {
- t.Errorf("%v: Unexpected error: %v", name, errs)
- }
- if !tc.valid && len(errs) == 0 {
- t.Errorf("%v: Unexpected non-error", name)
- }
- }
- }
- func TestValidateSSHAuthSecret(t *testing.T) {
- validSSHAuthSecret := func() core.Secret {
- return core.Secret{
- ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
- Type: core.SecretTypeSSHAuth,
- Data: map[string][]byte{
- core.SSHAuthPrivateKey: []byte("foo-bar-baz"),
- },
- }
- }
- missingSSHAuthPrivateKey := validSSHAuthSecret()
- delete(missingSSHAuthPrivateKey.Data, core.SSHAuthPrivateKey)
- tests := map[string]struct {
- secret core.Secret
- valid bool
- }{
- "valid": {validSSHAuthSecret(), true},
- "missing private key": {missingSSHAuthPrivateKey, false},
- }
- for name, tc := range tests {
- errs := ValidateSecret(&tc.secret)
- if tc.valid && len(errs) > 0 {
- t.Errorf("%v: Unexpected error: %v", name, errs)
- }
- if !tc.valid && len(errs) == 0 {
- t.Errorf("%v: Unexpected non-error", name)
- }
- }
- }
- func TestValidateEndpoints(t *testing.T) {
- successCases := map[string]core.Endpoints{
- "simple endpoint": {
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}},
- Ports: []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
- },
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
- Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}},
- },
- },
- },
- "empty subsets": {
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- },
- "no name required for singleton port": {
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
- Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}},
- },
- },
- },
- "empty ports": {
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
- },
- },
- },
- }
- for k, v := range successCases {
- if errs := ValidateEndpoints(&v); len(errs) != 0 {
- t.Errorf("Expected success for %s, got %v", k, errs)
- }
- }
- errorCases := map[string]struct {
- endpoints core.Endpoints
- errorType field.ErrorType
- errorDetail string
- }{
- "missing namespace": {
- endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}},
- errorType: "FieldValueRequired",
- },
- "missing name": {
- endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace"}},
- errorType: "FieldValueRequired",
- },
- "invalid namespace": {
- endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}},
- errorType: "FieldValueInvalid",
- errorDetail: dnsLabelErrMsg,
- },
- "invalid name": {
- endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}},
- errorType: "FieldValueInvalid",
- errorDetail: dnsSubdomainLabelErrMsg,
- },
- "empty addresses": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueRequired",
- },
- "invalid IP": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "[2001:0db8:85a3:0042:1000:8a2e:0370:7334]"}},
- Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueInvalid",
- errorDetail: "must be a valid IP address",
- },
- "Multiple ports, one without name": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
- Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueRequired",
- },
- "Invalid port number": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
- Ports: []core.EndpointPort{{Name: "a", Port: 66000, Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueInvalid",
- errorDetail: "between",
- },
- "Invalid protocol": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
- Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "Protocol"}},
- },
- },
- },
- errorType: "FieldValueNotSupported",
- },
- "Address missing IP": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{}},
- Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueInvalid",
- errorDetail: "must be a valid IP address",
- },
- "Port missing number": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
- Ports: []core.EndpointPort{{Name: "a", Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueInvalid",
- errorDetail: "between",
- },
- "Port missing protocol": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
- Ports: []core.EndpointPort{{Name: "a", Port: 93}},
- },
- },
- },
- errorType: "FieldValueRequired",
- },
- "Address is loopback": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "127.0.0.1"}},
- Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueInvalid",
- errorDetail: "loopback",
- },
- "Address is link-local": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "169.254.169.254"}},
- Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueInvalid",
- errorDetail: "link-local",
- },
- "Address is link-local multicast": {
- endpoints: core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
- Subsets: []core.EndpointSubset{
- {
- Addresses: []core.EndpointAddress{{IP: "224.0.0.1"}},
- Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP"}},
- },
- },
- },
- errorType: "FieldValueInvalid",
- errorDetail: "link-local multicast",
- },
- }
- for k, v := range errorCases {
- if errs := ValidateEndpoints(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
- t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
- }
- }
- }
- func TestValidateTLSSecret(t *testing.T) {
- successCases := map[string]core.Secret{
- "empty certificate chain": {
- ObjectMeta: metav1.ObjectMeta{Name: "tls-cert", Namespace: "namespace"},
- Data: map[string][]byte{
- core.TLSCertKey: []byte("public key"),
- core.TLSPrivateKeyKey: []byte("private key"),
- },
- },
- }
- for k, v := range successCases {
- if errs := ValidateSecret(&v); len(errs) != 0 {
- t.Errorf("Expected success for %s, got %v", k, errs)
- }
- }
- errorCases := map[string]struct {
- secrets core.Secret
- errorType field.ErrorType
- errorDetail string
- }{
- "missing public key": {
- secrets: core.Secret{
- ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
- Data: map[string][]byte{
- core.TLSCertKey: []byte("public key"),
- },
- },
- errorType: "FieldValueRequired",
- },
- "missing private key": {
- secrets: core.Secret{
- ObjectMeta: metav1.ObjectMeta{Name: "tls-cert"},
- Data: map[string][]byte{
- core.TLSCertKey: []byte("public key"),
- },
- },
- errorType: "FieldValueRequired",
- },
- }
- for k, v := range errorCases {
- if errs := ValidateSecret(&v.secrets); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
- t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
- }
- }
- }
- func TestValidateSecurityContext(t *testing.T) {
- runAsUser := int64(1)
- fullValidSC := func() *core.SecurityContext {
- return &core.SecurityContext{
- Privileged: utilpointer.BoolPtr(false),
- Capabilities: &core.Capabilities{
- Add: []core.Capability{"foo"},
- Drop: []core.Capability{"bar"},
- },
- SELinuxOptions: &core.SELinuxOptions{
- User: "user",
- Role: "role",
- Type: "type",
- Level: "level",
- },
- RunAsUser: &runAsUser,
- }
- }
- //setup data
- allSettings := fullValidSC()
- noCaps := fullValidSC()
- noCaps.Capabilities = nil
- noSELinux := fullValidSC()
- noSELinux.SELinuxOptions = nil
- noPrivRequest := fullValidSC()
- noPrivRequest.Privileged = nil
- noRunAsUser := fullValidSC()
- noRunAsUser.RunAsUser = nil
- successCases := map[string]struct {
- sc *core.SecurityContext
- }{
- "all settings": {allSettings},
- "no capabilities": {noCaps},
- "no selinux": {noSELinux},
- "no priv request": {noPrivRequest},
- "no run as user": {noRunAsUser},
- }
- for k, v := range successCases {
- if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("[%s] Expected success, got %v", k, errs)
- }
- }
- privRequestWithGlobalDeny := fullValidSC()
- privRequestWithGlobalDeny.Privileged = utilpointer.BoolPtr(true)
- negativeRunAsUser := fullValidSC()
- negativeUser := int64(-1)
- negativeRunAsUser.RunAsUser = &negativeUser
- privWithoutEscalation := fullValidSC()
- privWithoutEscalation.Privileged = utilpointer.BoolPtr(true)
- privWithoutEscalation.AllowPrivilegeEscalation = utilpointer.BoolPtr(false)
- capSysAdminWithoutEscalation := fullValidSC()
- capSysAdminWithoutEscalation.Capabilities.Add = []core.Capability{"CAP_SYS_ADMIN"}
- capSysAdminWithoutEscalation.AllowPrivilegeEscalation = utilpointer.BoolPtr(false)
- errorCases := map[string]struct {
- sc *core.SecurityContext
- errorType field.ErrorType
- errorDetail string
- capAllowPriv bool
- }{
- "request privileged when capabilities forbids": {
- sc: privRequestWithGlobalDeny,
- errorType: "FieldValueForbidden",
- errorDetail: "disallowed by cluster policy",
- },
- "negative RunAsUser": {
- sc: negativeRunAsUser,
- errorType: "FieldValueInvalid",
- errorDetail: "must be between",
- },
- "with CAP_SYS_ADMIN and allowPrivilegeEscalation false": {
- sc: capSysAdminWithoutEscalation,
- errorType: "FieldValueInvalid",
- errorDetail: "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN",
- },
- "with privileged and allowPrivilegeEscalation false": {
- sc: privWithoutEscalation,
- errorType: "FieldValueInvalid",
- errorDetail: "cannot set `allowPrivilegeEscalation` to false and `privileged` to true",
- capAllowPriv: true,
- },
- }
- for k, v := range errorCases {
- capabilities.SetForTests(capabilities.Capabilities{
- AllowPrivileged: v.capAllowPriv,
- })
- if errs := ValidateSecurityContext(v.sc, field.NewPath("field")); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
- t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
- }
- }
- }
- func fakeValidSecurityContext(priv bool) *core.SecurityContext {
- return &core.SecurityContext{
- Privileged: &priv,
- }
- }
- func TestValidPodLogOptions(t *testing.T) {
- now := metav1.Now()
- negative := int64(-1)
- zero := int64(0)
- positive := int64(1)
- tests := []struct {
- opt core.PodLogOptions
- errs int
- }{
- {core.PodLogOptions{}, 0},
- {core.PodLogOptions{Previous: true}, 0},
- {core.PodLogOptions{Follow: true}, 0},
- {core.PodLogOptions{TailLines: &zero}, 0},
- {core.PodLogOptions{TailLines: &negative}, 1},
- {core.PodLogOptions{TailLines: &positive}, 0},
- {core.PodLogOptions{LimitBytes: &zero}, 1},
- {core.PodLogOptions{LimitBytes: &negative}, 1},
- {core.PodLogOptions{LimitBytes: &positive}, 0},
- {core.PodLogOptions{SinceSeconds: &negative}, 1},
- {core.PodLogOptions{SinceSeconds: &positive}, 0},
- {core.PodLogOptions{SinceSeconds: &zero}, 1},
- {core.PodLogOptions{SinceTime: &now}, 0},
- }
- for i, test := range tests {
- errs := ValidatePodLogOptions(&test.opt)
- if test.errs != len(errs) {
- t.Errorf("%d: Unexpected errors: %v", i, errs)
- }
- }
- }
- func TestValidateConfigMap(t *testing.T) {
- newConfigMap := func(name, namespace string, data map[string]string, binaryData map[string][]byte) core.ConfigMap {
- return core.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: namespace,
- },
- Data: data,
- BinaryData: binaryData,
- }
- }
- var (
- validConfigMap = newConfigMap("validname", "validns", map[string]string{"key": "value"}, map[string][]byte{"bin": []byte("value")})
- maxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 253): "value"}, nil)
- emptyName = newConfigMap("", "validns", nil, nil)
- invalidName = newConfigMap("NoUppercaseOrSpecialCharsLike=Equals", "validns", nil, nil)
- emptyNs = newConfigMap("validname", "", nil, nil)
- invalidNs = newConfigMap("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil, nil)
- invalidKey = newConfigMap("validname", "validns", map[string]string{"a*b": "value"}, nil)
- leadingDotKey = newConfigMap("validname", "validns", map[string]string{".ab": "value"}, nil)
- dotKey = newConfigMap("validname", "validns", map[string]string{".": "value"}, nil)
- doubleDotKey = newConfigMap("validname", "validns", map[string]string{"..": "value"}, nil)
- overMaxKeyLength = newConfigMap("validname", "validns", map[string]string{strings.Repeat("a", 254): "value"}, nil)
- overMaxSize = newConfigMap("validname", "validns", map[string]string{"key": strings.Repeat("a", v1.MaxSecretSize+1)}, nil)
- duplicatedKey = newConfigMap("validname", "validns", map[string]string{"key": "value1"}, map[string][]byte{"key": []byte("value2")})
- binDataInvalidKey = newConfigMap("validname", "validns", nil, map[string][]byte{"a*b": []byte("value")})
- binDataLeadingDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{".ab": []byte("value")})
- binDataDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{".": []byte("value")})
- binDataDoubleDotKey = newConfigMap("validname", "validns", nil, map[string][]byte{"..": []byte("value")})
- binDataOverMaxKeyLength = newConfigMap("validname", "validns", nil, map[string][]byte{strings.Repeat("a", 254): []byte("value")})
- binDataOverMaxSize = newConfigMap("validname", "validns", nil, map[string][]byte{"bin": bytes.Repeat([]byte("a"), v1.MaxSecretSize+1)})
- binNonUtf8Value = newConfigMap("validname", "validns", nil, map[string][]byte{"key": {0, 0xFE, 0, 0xFF}})
- )
- tests := map[string]struct {
- cfg core.ConfigMap
- isValid bool
- }{
- "valid": {validConfigMap, true},
- "max key length": {maxKeyLength, true},
- "leading dot key": {leadingDotKey, true},
- "empty name": {emptyName, false},
- "invalid name": {invalidName, false},
- "invalid key": {invalidKey, false},
- "empty namespace": {emptyNs, false},
- "invalid namespace": {invalidNs, false},
- "dot key": {dotKey, false},
- "double dot key": {doubleDotKey, false},
- "over max key length": {overMaxKeyLength, false},
- "over max size": {overMaxSize, false},
- "duplicated key": {duplicatedKey, false},
- "binary data invalid key": {binDataInvalidKey, false},
- "binary data leading dot key": {binDataLeadingDotKey, true},
- "binary data dot key": {binDataDotKey, false},
- "binary data double dot key": {binDataDoubleDotKey, false},
- "binary data over max key length": {binDataOverMaxKeyLength, false},
- "binary data max size": {binDataOverMaxSize, false},
- "binary data non utf-8 bytes": {binNonUtf8Value, true},
- }
- for name, tc := range tests {
- errs := ValidateConfigMap(&tc.cfg)
- if tc.isValid && len(errs) > 0 {
- t.Errorf("%v: unexpected error: %v", name, errs)
- }
- if !tc.isValid && len(errs) == 0 {
- t.Errorf("%v: unexpected non-error", name)
- }
- }
- }
- func TestValidateConfigMapUpdate(t *testing.T) {
- newConfigMap := func(version, name, namespace string, data map[string]string) core.ConfigMap {
- return core.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: namespace,
- ResourceVersion: version,
- },
- Data: data,
- }
- }
- validConfigMap := func() core.ConfigMap {
- return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"})
- }
- falseVal := false
- trueVal := true
- configMap := validConfigMap()
- immutableConfigMap := validConfigMap()
- immutableConfigMap.Immutable = &trueVal
- mutableConfigMap := validConfigMap()
- mutableConfigMap.Immutable = &falseVal
- configMapWithData := validConfigMap()
- configMapWithData.Data["key-2"] = "value-2"
- immutableConfigMapWithData := validConfigMap()
- immutableConfigMapWithData.Immutable = &trueVal
- immutableConfigMapWithData.Data["key-2"] = "value-2"
- configMapWithChangedData := validConfigMap()
- configMapWithChangedData.Data["key"] = "foo"
- immutableConfigMapWithChangedData := validConfigMap()
- immutableConfigMapWithChangedData.Immutable = &trueVal
- immutableConfigMapWithChangedData.Data["key"] = "foo"
- noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
- cases := []struct {
- name string
- newCfg core.ConfigMap
- oldCfg core.ConfigMap
- valid bool
- }{
- {
- name: "valid",
- newCfg: configMap,
- oldCfg: configMap,
- valid: true,
- },
- {
- name: "invalid",
- newCfg: noVersion,
- oldCfg: configMap,
- valid: false,
- },
- {
- name: "mark configmap immutable",
- oldCfg: configMap,
- newCfg: immutableConfigMap,
- valid: true,
- },
- {
- name: "revert immutable configmap",
- oldCfg: immutableConfigMap,
- newCfg: configMap,
- valid: false,
- },
- {
- name: "mark immutable configmap mutable",
- oldCfg: immutableConfigMap,
- newCfg: mutableConfigMap,
- valid: false,
- },
- {
- name: "add data in configmap",
- oldCfg: configMap,
- newCfg: configMapWithData,
- valid: true,
- },
- {
- name: "add data in immutable configmap",
- oldCfg: immutableConfigMap,
- newCfg: immutableConfigMapWithData,
- valid: false,
- },
- {
- name: "change data in configmap",
- oldCfg: configMap,
- newCfg: configMapWithChangedData,
- valid: true,
- },
- {
- name: "change data in immutable configmap",
- oldCfg: immutableConfigMap,
- newCfg: immutableConfigMapWithChangedData,
- valid: false,
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
- if tc.valid && len(errs) > 0 {
- t.Errorf("Unexpected error: %v", errs)
- }
- if !tc.valid && len(errs) == 0 {
- t.Errorf("Unexpected lack of error")
- }
- })
- }
- }
- func TestValidateHasLabel(t *testing.T) {
- successCase := metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Labels: map[string]string{
- "other": "blah",
- "foo": "bar",
- },
- }
- if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- missingCase := metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Labels: map[string]string{
- "other": "blah",
- },
- }
- if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
- t.Errorf("expected failure")
- }
- wrongValueCase := metav1.ObjectMeta{
- Name: "123",
- Namespace: "ns",
- Labels: map[string]string{
- "other": "blah",
- "foo": "notbar",
- },
- }
- if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 {
- t.Errorf("expected failure")
- }
- }
- func TestIsValidSysctlName(t *testing.T) {
- valid := []string{
- "a.b.c.d",
- "a",
- "a_b",
- "a-b",
- "abc",
- "abc.def",
- }
- invalid := []string{
- "",
- "*",
- "ä",
- "a_",
- "_",
- "__",
- "_a",
- "_a._b",
- "-",
- ".",
- "a.",
- ".a",
- "a.b.",
- "a*.b",
- "a*b",
- "*a",
- "a.*",
- "*",
- "abc*",
- "a.abc*",
- "a.b.*",
- "Abc",
- func(n int) string {
- x := make([]byte, n)
- for i := range x {
- x[i] = byte('a')
- }
- return string(x)
- }(256),
- }
- for _, s := range valid {
- if !IsValidSysctlName(s) {
- t.Errorf("%q expected to be a valid sysctl name", s)
- }
- }
- for _, s := range invalid {
- if IsValidSysctlName(s) {
- t.Errorf("%q expected to be an invalid sysctl name", s)
- }
- }
- }
- func TestValidateSysctls(t *testing.T) {
- valid := []string{
- "net.foo.bar",
- "kernel.shmmax",
- }
- invalid := []string{
- "i..nvalid",
- "_invalid",
- }
- duplicates := []string{
- "kernel.shmmax",
- "kernel.shmmax",
- }
- sysctls := make([]core.Sysctl, len(valid))
- for i, sysctl := range valid {
- sysctls[i].Name = sysctl
- }
- errs := validateSysctls(sysctls, field.NewPath("foo"))
- if len(errs) != 0 {
- t.Errorf("unexpected validation errors: %v", errs)
- }
- sysctls = make([]core.Sysctl, len(invalid))
- for i, sysctl := range invalid {
- sysctls[i].Name = sysctl
- }
- errs = validateSysctls(sysctls, field.NewPath("foo"))
- if len(errs) != 2 {
- t.Errorf("expected 2 validation errors. Got: %v", errs)
- } else {
- if got, expected := errs[0].Error(), "foo"; !strings.Contains(got, expected) {
- t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
- }
- if got, expected := errs[1].Error(), "foo"; !strings.Contains(got, expected) {
- t.Errorf("unexpected errors: expected=%q, got=%q", expected, got)
- }
- }
- sysctls = make([]core.Sysctl, len(duplicates))
- for i, sysctl := range duplicates {
- sysctls[i].Name = sysctl
- }
- errs = validateSysctls(sysctls, field.NewPath("foo"))
- if len(errs) != 1 {
- t.Errorf("unexpected validation errors: %v", errs)
- } else if errs[0].Type != field.ErrorTypeDuplicate {
- t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
- }
- }
- func newNodeNameEndpoint(nodeName string) *core.Endpoints {
- ep := &core.Endpoints{
- ObjectMeta: metav1.ObjectMeta{
- Name: "foo",
- Namespace: metav1.NamespaceDefault,
- ResourceVersion: "1",
- },
- Subsets: []core.EndpointSubset{
- {
- NotReadyAddresses: []core.EndpointAddress{},
- Ports: []core.EndpointPort{{Name: "https", Port: 443, Protocol: "TCP"}},
- Addresses: []core.EndpointAddress{
- {
- IP: "8.8.8.8",
- Hostname: "zookeeper1",
- NodeName: &nodeName}}}}}
- return ep
- }
- func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) {
- oldEndpoint := newNodeNameEndpoint("kubernetes-node-setup-by-backend")
- updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename")
- // Check that NodeName can be changed during update, this is to accommodate the case where nodeIP or PodCIDR is reused.
- // The same ip will now have a different nodeName.
- errList := ValidateEndpoints(updatedEndpoint)
- errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...)
- if len(errList) != 0 {
- t.Error("Endpoint should allow changing of Subset.Addresses.NodeName on update")
- }
- }
- func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) {
- // Check NodeName DNS validation
- endpoint := newNodeNameEndpoint("illegal*.nodename")
- errList := ValidateEndpoints(endpoint)
- if len(errList) == 0 {
- t.Error("Endpoint should reject invalid NodeName")
- }
- }
- func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) {
- endpoint := newNodeNameEndpoint("10.10.1.1")
- errList := ValidateEndpoints(endpoint)
- if len(errList) != 0 {
- t.Error("Endpoint should accept a NodeName that is an IP address")
- }
- }
- func TestValidateFlexVolumeSource(t *testing.T) {
- testcases := map[string]struct {
- source *core.FlexVolumeSource
- expectedErrs map[string]string
- }{
- "valid": {
- source: &core.FlexVolumeSource{Driver: "foo"},
- expectedErrs: map[string]string{},
- },
- "valid with options": {
- source: &core.FlexVolumeSource{Driver: "foo", Options: map[string]string{"foo": "bar"}},
- expectedErrs: map[string]string{},
- },
- "no driver": {
- source: &core.FlexVolumeSource{Driver: ""},
- expectedErrs: map[string]string{"driver": "Required value"},
- },
- "reserved option keys": {
- source: &core.FlexVolumeSource{
- Driver: "foo",
- Options: map[string]string{
- // valid options
- "myns.io": "A",
- "myns.io/bar": "A",
- "myns.io/kubernetes.io": "A",
- // invalid options
- "KUBERNETES.IO": "A",
- "kubernetes.io": "A",
- "kubernetes.io/": "A",
- "kubernetes.io/foo": "A",
- "alpha.kubernetes.io": "A",
- "alpha.kubernetes.io/": "A",
- "alpha.kubernetes.io/foo": "A",
- "k8s.io": "A",
- "k8s.io/": "A",
- "k8s.io/foo": "A",
- "alpha.k8s.io": "A",
- "alpha.k8s.io/": "A",
- "alpha.k8s.io/foo": "A",
- },
- },
- expectedErrs: map[string]string{
- "options[KUBERNETES.IO]": "reserved",
- "options[kubernetes.io]": "reserved",
- "options[kubernetes.io/]": "reserved",
- "options[kubernetes.io/foo]": "reserved",
- "options[alpha.kubernetes.io]": "reserved",
- "options[alpha.kubernetes.io/]": "reserved",
- "options[alpha.kubernetes.io/foo]": "reserved",
- "options[k8s.io]": "reserved",
- "options[k8s.io/]": "reserved",
- "options[k8s.io/foo]": "reserved",
- "options[alpha.k8s.io]": "reserved",
- "options[alpha.k8s.io/]": "reserved",
- "options[alpha.k8s.io/foo]": "reserved",
- },
- },
- }
- for k, tc := range testcases {
- errs := validateFlexVolumeSource(tc.source, nil)
- for _, err := range errs {
- expectedErr, ok := tc.expectedErrs[err.Field]
- if !ok {
- t.Errorf("%s: unexpected err on field %s: %v", k, err.Field, err)
- continue
- }
- if !strings.Contains(err.Error(), expectedErr) {
- t.Errorf("%s: expected err on field %s to contain '%s', was %v", k, err.Field, expectedErr, err.Error())
- continue
- }
- }
- if len(errs) != len(tc.expectedErrs) {
- t.Errorf("%s: expected errs %#v, got %#v", k, tc.expectedErrs, errs)
- continue
- }
- }
- }
- func TestValidateOrSetClientIPAffinityConfig(t *testing.T) {
- successCases := map[string]*core.SessionAffinityConfig{
- "non-empty config, valid timeout: 1": {
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(1),
- },
- },
- "non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds-1": {
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(core.MaxClientIPServiceAffinitySeconds - 1),
- },
- },
- "non-empty config, valid timeout: core.MaxClientIPServiceAffinitySeconds": {
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(core.MaxClientIPServiceAffinitySeconds),
- },
- },
- }
- for name, test := range successCases {
- if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 {
- t.Errorf("case: %s, expected success: %v", name, errs)
- }
- }
- errorCases := map[string]*core.SessionAffinityConfig{
- "empty session affinity config": nil,
- "empty client IP config": {
- ClientIP: nil,
- },
- "empty timeoutSeconds": {
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: nil,
- },
- },
- "non-empty config, invalid timeout: core.MaxClientIPServiceAffinitySeconds+1": {
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(core.MaxClientIPServiceAffinitySeconds + 1),
- },
- },
- "non-empty config, invalid timeout: -1": {
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(-1),
- },
- },
- "non-empty config, invalid timeout: 0": {
- ClientIP: &core.ClientIPConfig{
- TimeoutSeconds: utilpointer.Int32Ptr(0),
- },
- },
- }
- for name, test := range errorCases {
- if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 {
- t.Errorf("case: %v, expected failures: %v", name, errs)
- }
- }
- }
- func TestValidateWindowsSecurityContextOptions(t *testing.T) {
- toPtr := func(s string) *string {
- return &s
- }
- testCases := []struct {
- testName string
- windowsOptions *core.WindowsSecurityContextOptions
- expectedErrorSubstring string
- }{
- {
- testName: "a nil pointer",
- },
- {
- testName: "an empty struct",
- windowsOptions: &core.WindowsSecurityContextOptions{},
- },
- {
- testName: "a valid input",
- windowsOptions: &core.WindowsSecurityContextOptions{
- GMSACredentialSpecName: toPtr("dummy-gmsa-crep-spec-name"),
- GMSACredentialSpec: toPtr("dummy-gmsa-crep-spec-contents"),
- },
- },
- {
- testName: "a GMSA cred spec name that is not a valid resource name",
- windowsOptions: &core.WindowsSecurityContextOptions{
- // invalid because of the underscore
- GMSACredentialSpecName: toPtr("not_a-valid-gmsa-crep-spec-name"),
- },
- expectedErrorSubstring: dnsSubdomainLabelErrMsg,
- },
- {
- testName: "empty GMSA cred spec contents",
- windowsOptions: &core.WindowsSecurityContextOptions{
- GMSACredentialSpec: toPtr(""),
- },
- expectedErrorSubstring: "gmsaCredentialSpec cannot be an empty string",
- },
- {
- testName: "GMSA cred spec contents that are too long",
- windowsOptions: &core.WindowsSecurityContextOptions{
- GMSACredentialSpec: toPtr(strings.Repeat("a", maxGMSACredentialSpecLength+1)),
- },
- expectedErrorSubstring: "gmsaCredentialSpec size must be under",
- },
- {
- testName: "RunAsUserName is nil",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: nil,
- },
- },
- {
- testName: "a valid RunAsUserName",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("Container. User"),
- },
- },
- {
- testName: "a valid RunAsUserName with NetBios Domain",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("Network Service\\Container. User"),
- },
- },
- {
- testName: "a valid RunAsUserName with DNS Domain",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".liSH\\Container. User"),
- },
- },
- {
- testName: "a valid RunAsUserName with DNS Domain with a single character segment",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr(strings.Repeat("fOo", 20) + ".l\\Container. User"),
- },
- },
- {
- testName: "a valid RunAsUserName with a long single segment DNS Domain",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr(strings.Repeat("a", 42) + "\\Container. User"),
- },
- },
- {
- testName: "an empty RunAsUserName",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr(""),
- },
- expectedErrorSubstring: "runAsUserName cannot be an empty string",
- },
- {
- testName: "RunAsUserName containing a control character",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("Container\tUser"),
- },
- expectedErrorSubstring: "runAsUserName cannot contain control characters",
- },
- {
- testName: "RunAsUserName containing too many backslashes",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("Container\\Foo\\Lish"),
- },
- expectedErrorSubstring: "runAsUserName cannot contain more than one backslash",
- },
- {
- testName: "RunAsUserName containing backslash but empty Domain",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("\\User"),
- },
- expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
- },
- {
- testName: "RunAsUserName containing backslash but empty User",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("Container\\"),
- },
- expectedErrorSubstring: "runAsUserName's User cannot be empty",
- },
- {
- testName: "RunAsUserName's NetBios Domain is too long",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("NetBios " + strings.Repeat("a", 8) + "\\user"),
- },
- expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
- },
- {
- testName: "RunAsUserName's DNS Domain is too long",
- windowsOptions: &core.WindowsSecurityContextOptions{
- // even if this tests the max Domain length, the Domain should still be "valid".
- RunAsUserName: toPtr(strings.Repeat(strings.Repeat("a", 63)+".", 4)[:253] + ".com\\user"),
- },
- expectedErrorSubstring: "runAsUserName's Domain length must be under",
- },
- {
- testName: "RunAsUserName's User is too long",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr(strings.Repeat("a", maxRunAsUserNameUserLength+1)),
- },
- expectedErrorSubstring: "runAsUserName's User length must not be longer than",
- },
- {
- testName: "RunAsUserName's User cannot contain only spaces or periods",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("... ..."),
- },
- expectedErrorSubstring: "runAsUserName's User cannot contain only periods or spaces",
- },
- {
- testName: "RunAsUserName's NetBios Domain cannot start with a dot",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr(".FooLish\\User"),
- },
- expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
- },
- {
- testName: "RunAsUserName's NetBios Domain cannot contain invalid characters",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("Foo? Lish?\\User"),
- },
- expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios",
- },
- {
- testName: "RunAsUserName's DNS Domain cannot contain invalid characters",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr(strings.Repeat("a", 32) + ".com-\\user"),
- },
- expectedErrorSubstring: "runAsUserName's Domain doesn't match the NetBios nor the DNS format",
- },
- {
- testName: "RunAsUserName's User cannot contain invalid characters",
- windowsOptions: &core.WindowsSecurityContextOptions{
- RunAsUserName: toPtr("Container/User"),
- },
- expectedErrorSubstring: "runAsUserName's User cannot contain the following characters",
- },
- }
- for _, testCase := range testCases {
- t.Run("validateWindowsSecurityContextOptions with"+testCase.testName, func(t *testing.T) {
- errs := validateWindowsSecurityContextOptions(testCase.windowsOptions, field.NewPath("field"))
- switch len(errs) {
- case 0:
- if testCase.expectedErrorSubstring != "" {
- t.Errorf("expected a failure containing the substring: %q", testCase.expectedErrorSubstring)
- }
- case 1:
- if testCase.expectedErrorSubstring == "" {
- t.Errorf("didn't expect a failure, got: %q", errs[0].Error())
- } else if !strings.Contains(errs[0].Error(), testCase.expectedErrorSubstring) {
- t.Errorf("expected a failure with the substring %q, got %q instead", testCase.expectedErrorSubstring, errs[0].Error())
- }
- default:
- t.Errorf("got %d failures", len(errs))
- for i, err := range errs {
- t.Errorf("error %d: %q", i, err.Error())
- }
- }
- })
- }
- }
- func testDataSourceInSpec(name string, kind string, apiGroup string) *core.PersistentVolumeClaimSpec {
- scName := "csi-plugin"
- dataSourceInSpec := core.PersistentVolumeClaimSpec{
- AccessModes: []core.PersistentVolumeAccessMode{
- core.ReadOnlyMany,
- },
- Resources: core.ResourceRequirements{
- Requests: core.ResourceList{
- core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
- },
- },
- StorageClassName: &scName,
- DataSource: &core.TypedLocalObjectReference{
- APIGroup: &apiGroup,
- Kind: kind,
- Name: name,
- },
- }
- return &dataSourceInSpec
- }
- func TestAlphaVolumePVCDataSource(t *testing.T) {
- testCases := []struct {
- testName string
- claimSpec core.PersistentVolumeClaimSpec
- expectedFail bool
- }{
- {
- testName: "test create from valid snapshot source",
- claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "snapshot.storage.k8s.io"),
- },
- {
- testName: "test create from valid pvc source",
- claimSpec: *testDataSourceInSpec("test_pvc", "PersistentVolumeClaim", ""),
- },
- {
- testName: "test missing name in snapshot datasource should fail",
- claimSpec: *testDataSourceInSpec("", "VolumeSnapshot", "snapshot.storage.k8s.io"),
- expectedFail: true,
- },
- {
- testName: "test specifying pvc with snapshot api group should fail",
- claimSpec: *testDataSourceInSpec("test_snapshot", "PersistentVolumeClaim", "snapshot.storage.k8s.io"),
- expectedFail: true,
- },
- {
- testName: "test invalid group name in snapshot datasource should fail",
- claimSpec: *testDataSourceInSpec("test_snapshot", "VolumeSnapshot", "storage.k8s.io"),
- expectedFail: true,
- },
- }
- for _, tc := range testCases {
- if tc.expectedFail {
- if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec")); len(errs) == 0 {
- t.Errorf("expected failure: %v", errs)
- }
- } else {
- if errs := ValidatePersistentVolumeClaimSpec(&tc.claimSpec, field.NewPath("spec")); len(errs) != 0 {
- t.Errorf("expected success: %v", errs)
- }
- }
- }
- }
- func TestValidateTopologySpreadConstraints(t *testing.T) {
- defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EvenPodsSpread, true)()
- testCases := []struct {
- name string
- constraints []core.TopologySpreadConstraint
- errtype field.ErrorType
- errfield string
- }{
- {
- name: "all required fields ok",
- constraints: []core.TopologySpreadConstraint{
- {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
- },
- },
- {
- name: "missing MaxSkew",
- constraints: []core.TopologySpreadConstraint{
- {TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
- },
- errtype: field.ErrorTypeInvalid,
- errfield: "maxSkew",
- },
- {
- name: "invalid MaxSkew",
- constraints: []core.TopologySpreadConstraint{
- {MaxSkew: 0, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
- },
- errtype: field.ErrorTypeInvalid,
- errfield: "maxSkew",
- },
- {
- name: "missing TopologyKey",
- constraints: []core.TopologySpreadConstraint{
- {MaxSkew: 1, WhenUnsatisfiable: core.DoNotSchedule},
- },
- errtype: field.ErrorTypeRequired,
- errfield: "topologyKey",
- },
- {
- name: "missing scheduling mode",
- constraints: []core.TopologySpreadConstraint{
- {MaxSkew: 1, TopologyKey: "k8s.io/zone"},
- },
- errtype: field.ErrorTypeNotSupported,
- errfield: "whenUnsatisfiable",
- },
- {
- name: "unsupported scheduling mode",
- constraints: []core.TopologySpreadConstraint{
- {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.UnsatisfiableConstraintAction("N/A")},
- },
- errtype: field.ErrorTypeNotSupported,
- errfield: "whenUnsatisfiable",
- },
- {
- name: "multiple constraints ok with all required fields",
- constraints: []core.TopologySpreadConstraint{
- {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
- {MaxSkew: 2, TopologyKey: "k8s.io/node", WhenUnsatisfiable: core.ScheduleAnyway},
- },
- },
- {
- name: "multiple constraints missing TopologyKey on partial ones",
- constraints: []core.TopologySpreadConstraint{
- {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
- {MaxSkew: 2, WhenUnsatisfiable: core.ScheduleAnyway},
- },
- errtype: field.ErrorTypeRequired,
- errfield: "topologyKey",
- },
- {
- name: "duplicate constraints",
- constraints: []core.TopologySpreadConstraint{
- {MaxSkew: 1, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
- {MaxSkew: 2, TopologyKey: "k8s.io/zone", WhenUnsatisfiable: core.DoNotSchedule},
- },
- errtype: field.ErrorTypeDuplicate,
- errfield: "{topologyKey, whenUnsatisfiable}",
- },
- }
- for i, tc := range testCases {
- errs := validateTopologySpreadConstraints(tc.constraints, field.NewPath("field"))
- if len(errs) > 0 && tc.errtype == "" {
- t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
- } else if len(errs) == 0 && tc.errtype != "" {
- t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype)
- } else if len(errs) >= 1 {
- if errs[0].Type != tc.errtype {
- t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type)
- } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) {
- t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field)
- }
- }
- }
- }
- func TestValidateOverhead(t *testing.T) {
- successCase := []struct {
- Name string
- overhead core.ResourceList
- }{
- {
- Name: "Valid Overhead for CPU + Memory",
- overhead: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("10G"),
- },
- },
- }
- for _, tc := range successCase {
- if errs := validateOverhead(tc.overhead, field.NewPath("overheads")); len(errs) != 0 {
- t.Errorf("%q unexpected error: %v", tc.Name, errs)
- }
- }
- errorCase := []struct {
- Name string
- overhead core.ResourceList
- }{
- {
- Name: "Invalid Overhead Resources",
- overhead: core.ResourceList{
- core.ResourceName("my.org"): resource.MustParse("10m"),
- },
- },
- }
- for _, tc := range errorCase {
- if errs := validateOverhead(tc.overhead, field.NewPath("resources")); len(errs) == 0 {
- t.Errorf("%q expected error", tc.Name)
- }
- }
- }
- // helper creates a pod with name, namespace and IPs
- func makePod(podName string, podNamespace string, podIPs []core.PodIP) core.Pod {
- return core.Pod{
- ObjectMeta: metav1.ObjectMeta{Name: podName, Namespace: podNamespace},
- Spec: core.PodSpec{
- Containers: []core.Container{
- {
- Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
- },
- },
- RestartPolicy: core.RestartPolicyAlways,
- DNSPolicy: core.DNSClusterFirst,
- },
- Status: core.PodStatus{
- PodIPs: podIPs,
- },
- }
- }
- func TestPodIPsValidation(t *testing.T) {
- testCases := []struct {
- pod core.Pod
- expectError bool
- }{
- {
- expectError: false,
- pod: makePod("nil-ips", "ns", nil),
- },
- {
- expectError: false,
- pod: makePod("empty-podips-list", "ns", []core.PodIP{}),
- },
- {
- expectError: false,
- pod: makePod("single-ip-family-6", "ns", []core.PodIP{{IP: "::1"}}),
- },
- {
- expectError: false,
- pod: makePod("single-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}}),
- },
- {
- expectError: false,
- pod: makePod("dual-stack-4-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}}),
- },
- {
- expectError: false,
- pod: makePod("dual-stack-6-4", "ns", []core.PodIP{{IP: "::1"}, {IP: "1.1.1.1"}}),
- },
- /* failure cases start here */
- {
- expectError: true,
- pod: makePod("invalid-pod-ip", "ns", []core.PodIP{{IP: "this-is-not-an-ip"}}),
- },
- {
- expectError: true,
- pod: makePod("dualstack-same-ip-family-6", "ns", []core.PodIP{{IP: "::1"}, {IP: "::2"}}),
- },
- {
- expectError: true,
- pod: makePod("dualstack-same-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}),
- },
- {
- expectError: true,
- pod: makePod("dualstack-repeated-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::2"}}),
- },
- {
- expectError: true,
- pod: makePod("dualstack-repeated-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "2.2.2.2"}}),
- },
- {
- expectError: true,
- pod: makePod("dualstack-duplicate-ip-family-4", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "1.1.1.1"}, {IP: "::1"}}),
- },
- {
- expectError: true,
- pod: makePod("dualstack-duplicate-ip-family-6", "ns", []core.PodIP{{IP: "1.1.1.1"}, {IP: "::1"}, {IP: "::1"}}),
- },
- }
- for _, testCase := range testCases {
- errs := ValidatePod(&testCase.pod)
- if len(errs) == 0 && testCase.expectError {
- t.Errorf("expected failure for %s, but there were none", testCase.pod.Name)
- return
- }
- if len(errs) != 0 && !testCase.expectError {
- t.Errorf("expected success for %s, but there were errors: %v", testCase.pod.Name, errs)
- return
- }
- }
- }
- // makes a node with pod cidr and a name
- func makeNode(nodeName string, podCIDRs []string) core.Node {
- return core.Node{
- ObjectMeta: metav1.ObjectMeta{
- Name: nodeName,
- },
- Status: core.NodeStatus{
- Addresses: []core.NodeAddress{
- {Type: core.NodeExternalIP, Address: "something"},
- },
- Capacity: core.ResourceList{
- core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
- core.ResourceName(core.ResourceMemory): resource.MustParse("0"),
- },
- },
- Spec: core.NodeSpec{
- PodCIDRs: podCIDRs,
- },
- }
- }
- func TestValidateNodeCIDRs(t *testing.T) {
- opts := NodeValidationOptions{
- ValidateSingleHugePageResource: true,
- }
- testCases := []struct {
- expectError bool
- node core.Node
- }{
- {
- expectError: false,
- node: makeNode("nil-pod-cidr", nil),
- },
- {
- expectError: false,
- node: makeNode("empty-pod-cidr", []string{}),
- },
- {
- expectError: false,
- node: makeNode("single-pod-cidr-4", []string{"192.168.0.0/16"}),
- },
- {
- expectError: false,
- node: makeNode("single-pod-cidr-6", []string{"2000::/10"}),
- },
- {
- expectError: false,
- node: makeNode("multi-pod-cidr-6-4", []string{"2000::/10", "192.168.0.0/16"}),
- },
- {
- expectError: false,
- node: makeNode("multi-pod-cidr-4-6", []string{"192.168.0.0/16", "2000::/10"}),
- },
- // error cases starts here
- {
- expectError: true,
- node: makeNode("invalid-pod-cidr", []string{"this-is-not-a-valid-cidr"}),
- },
- {
- expectError: true,
- node: makeNode("duplicate-pod-cidr-4", []string{"10.0.0.1/16", "10.0.0.1/16"}),
- },
- {
- expectError: true,
- node: makeNode("duplicate-pod-cidr-6", []string{"2000::/10", "2000::/10"}),
- },
- {
- expectError: true,
- node: makeNode("not-a-dualstack-no-v4", []string{"2000::/10", "3000::/10"}),
- },
- {
- expectError: true,
- node: makeNode("not-a-dualstack-no-v6", []string{"10.0.0.0/16", "10.1.0.0/16"}),
- },
- {
- expectError: true,
- node: makeNode("not-a-dualstack-repeated-v6", []string{"2000::/10", "10.0.0.0/16", "3000::/10"}),
- },
- {
- expectError: true,
- node: makeNode("not-a-dualstack-repeated-v4", []string{"10.0.0.0/16", "3000::/10", "10.1.0.0/16"}),
- },
- }
- for _, testCase := range testCases {
- errs := ValidateNode(&testCase.node, opts)
- if len(errs) == 0 && testCase.expectError {
- t.Errorf("expected failure for %s, but there were none", testCase.node.Name)
- return
- }
- if len(errs) != 0 && !testCase.expectError {
- t.Errorf("expected success for %s, but there were errors: %v", testCase.node.Name, errs)
- return
- }
- }
- }
|