dalitests.cpp 122 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems®.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. /*
  14. * Dali Quick Regression Suite: Tests Dali functionality on a programmatic way.
  15. *
  16. * Add as much as possible here to avoid having to run the Hthor/Thor regressions
  17. * all the time for Dali tests, since most of it can be tested quickly from here.
  18. */
  19. #ifdef _USE_CPPUNIT
  20. #include "mpbase.hpp"
  21. #include "mpcomm.hpp"
  22. #include "daclient.hpp"
  23. #include "dadfs.hpp"
  24. #include "dafdesc.hpp"
  25. #include "dasds.hpp"
  26. #include "danqs.hpp"
  27. #include "dautils.hpp"
  28. #include <vector>
  29. #include <future>
  30. #include <math.h>
  31. #include "unittests.hpp"
  32. //#define COMPAT
  33. // ======================================================================= Support Functions / Classes
  34. static __int64 subchangetotal;
  35. static unsigned subchangenum;
  36. static CriticalSection subchangesect;
  37. static IRemoteConnection *Rconn;
  38. static IDistributedFileDirectory & dir = queryDistributedFileDirectory();
  39. static IUserDescriptor *user = createUserDescriptor();
  40. static unsigned initCounter = 0; // counter for initialiser
  41. // Declared in dadfs.cpp *only* when CPPUNIT is active
  42. extern void removeLogical(const char *fname, IUserDescriptor *user);
  43. void daliClientInit()
  44. {
  45. // Only initialise on first pass
  46. if (initCounter != 0)
  47. return;
  48. InitModuleObjects();
  49. user->set("user", "passwd");
  50. // Connect to local Dali
  51. SocketEndpoint ep;
  52. ep.set(".", 7070);
  53. SocketEndpointArray epa;
  54. epa.append(ep);
  55. Owned<IGroup> group = createIGroup(epa);
  56. initClientProcess(group, DCR_Testing);
  57. initCounter++;
  58. }
  59. void daliClientEnd()
  60. {
  61. if (!initCounter)
  62. return;
  63. else if (1 == initCounter) // Only destroy on last pass
  64. {
  65. // Cleanup
  66. releaseAtoms();
  67. closedownClientProcess();
  68. setNodeCaching(false);
  69. }
  70. else
  71. initCounter--;
  72. }
  73. interface IChecker
  74. {
  75. virtual void title(unsigned n,const char *s)=0;
  76. virtual void add(const char *s,__int64 v)=0;
  77. virtual void add(const char *s,const char* v)=0;
  78. virtual void add(unsigned n,const char *s,__int64 v)=0;
  79. virtual void add(unsigned n,const char *s,const char* v)=0;
  80. virtual void error(const char *txt)=0;
  81. };
  82. void checkFilePart(IChecker *checker,IDistributedFilePart *part,bool blocked)
  83. {
  84. StringBuffer tmp;
  85. checker->add("getPartIndex",part->getPartIndex());
  86. unsigned n = part->numCopies();
  87. checker->add("numCopies",part->numCopies());
  88. checker->add("maxCopies",n);
  89. RemoteFilename rfn;
  90. for (unsigned copy=0;copy<n;copy++) {
  91. INode *node = part->queryNode(copy);
  92. if (node)
  93. checker->add(copy,"queryNode",node->endpoint().getUrlStr(tmp.clear()).str());
  94. else
  95. checker->error("missing node");
  96. checker->add(copy,"getFilename",part->getFilename(rfn,copy).getRemotePath(tmp.clear()).str());
  97. }
  98. checker->add("getPartName",part->getPartName(tmp.clear()).str());
  99. #ifndef COMPAT
  100. checker->add("getPartDirectory",part->getPartDirectory(tmp.clear()).str());
  101. #endif
  102. checker->add("queryProperties()",toXML(&part->queryAttributes(),tmp.clear()).str());
  103. checker->add("isHost",part->isHost()?1:0);
  104. checker->add("getFileSize",part->getFileSize(false,false));
  105. CDateTime dt;
  106. if (part->getModifiedTime(false,false,dt))
  107. dt.getString(tmp.clear());
  108. else
  109. tmp.clear().append("nodatetime");
  110. checker->add("getModifiedTime",tmp.str());
  111. unsigned crc;
  112. if (part->getCrc(crc)&&!blocked)
  113. checker->add("getCrc",crc);
  114. else
  115. checker->add("getCrc","nocrc");
  116. }
  117. void checkFile(IChecker *checker,IDistributedFile *file)
  118. {
  119. StringBuffer tmp;
  120. checker->add("queryLogicalName",file->queryLogicalName());
  121. unsigned np = file->numParts();
  122. checker->add("numParts",np);
  123. checker->add("queryDefaultDir",file->queryDefaultDir());
  124. if (np>1)
  125. checker->add("queryPartMask",file->queryPartMask());
  126. checker->add("queryProperties()",toXML(&file->queryAttributes(),tmp.clear()).str());
  127. CDateTime dt;
  128. if (file->getModificationTime(dt))
  129. dt.getString(tmp.clear());
  130. else
  131. tmp.clear().append("nodatetime");
  132. // Owned<IFileDescriptor> desc = getFileDescriptor();
  133. // checkFileDescriptor(checker,desc);
  134. //virtual bool existsPhysicalPartFiles(unsigned short port) = 0; // returns true if physical patrs all exist (on primary OR secondary)
  135. //Owned<IPropertyTree> tree = getTreeCopy();
  136. //checker->add("queryProperties()",toXML(tree,tmp.clear()).str());
  137. checker->add("getFileSize",file->getFileSize(false,false));
  138. bool blocked;
  139. checker->add("isCompressed",file->isCompressed(&blocked)?1:0);
  140. checker->add("blocked",blocked?1:0);
  141. unsigned csum;
  142. if (file->getFileCheckSum(csum)&&!blocked)
  143. checker->add("getFileCheckSum",csum);
  144. else
  145. checker->add("getFileCheckSum","nochecksum");
  146. StringBuffer clustname;
  147. checker->add("queryClusterName(0)",file->getClusterName(0,clustname).str());
  148. for (unsigned i=0;i<np;i++) {
  149. Owned<IDistributedFilePart> part = file->getPart(i);
  150. if (part)
  151. checkFilePart(checker,part,blocked);
  152. }
  153. }
  154. void checkFiles(const char *fn)
  155. {
  156. class cChecker: implements IChecker
  157. {
  158. public:
  159. virtual void title(unsigned n,const char *s)
  160. {
  161. printf("Title[%d]='%s'\n",n,s);
  162. }
  163. virtual void add(const char *s,__int64 v)
  164. {
  165. printf("%s=%" I64F "d\n",s,v);
  166. }
  167. virtual void add(const char *s,const char* v)
  168. {
  169. printf("%s='%s'\n",s,v);
  170. }
  171. virtual void add(unsigned n,const char *s,__int64 v)
  172. {
  173. printf("%s[%d]=%" I64F "d\n",s,n,v);
  174. }
  175. virtual void add(unsigned n,const char *s,const char* v)
  176. {
  177. printf("%s[%d]='%s'\n",s,n,v);
  178. }
  179. virtual void error(const char *txt)
  180. {
  181. printf("ERROR '%s'\n",txt);
  182. }
  183. } checker;
  184. unsigned start = msTick();
  185. unsigned slowest = 0;
  186. StringAttr slowname;
  187. if (fn) {
  188. checker.title(1,fn);
  189. try {
  190. Owned<IDistributedFile> file=queryDistributedFileDirectory().lookup(fn,user,AccessMode::tbdRead,false,false,nullptr,defaultNonPrivilegedUser);
  191. if (!file)
  192. printf("file '%s' not found\n",fn);
  193. else
  194. checkFile(&checker,file);
  195. }
  196. catch (IException *e) {
  197. StringBuffer str;
  198. e->errorMessage(str);
  199. e->Release();
  200. checker.error(str.str());
  201. }
  202. }
  203. else {
  204. Owned<IDistributedFileIterator> iter = queryDistributedFileDirectory().getIterator("*",false,user,defaultNonPrivilegedUser);
  205. unsigned i=0;
  206. unsigned ss = msTick();
  207. ForEach(*iter) {
  208. i++;
  209. StringBuffer lfname;
  210. iter->getName(lfname);
  211. checker.title(i,lfname.str());
  212. try {
  213. IDistributedFile &file=iter->query();
  214. checkFile(&checker,&file);
  215. unsigned t = (msTick()-ss);
  216. if (t>slowest) {
  217. slowest = t;
  218. slowname.set(lfname.str());
  219. }
  220. }
  221. catch (IException *e) {
  222. StringBuffer str;
  223. e->errorMessage(str);
  224. e->Release();
  225. checker.error(str.str());
  226. }
  227. ss = msTick();
  228. }
  229. }
  230. unsigned t = msTick()-start;
  231. printf("Complete in %ds\n",t/1000);
  232. if (!slowname.isEmpty())
  233. printf("Slowest %s = %dms\n",slowname.get(),slowest);
  234. };
  235. const char *filelist=
  236. "thor_data400::gong_delete_plus,"
  237. "thor_data400::in::npanxx,"
  238. "thor_data400::tpm_deduped,"
  239. "thor_data400::base::movers_ingest_ll,"
  240. "thor_hank::cemtemp::fldl,"
  241. "thor_data400::patch,"
  242. "thor_data400::in::flvehreg_01_prethor_upd200204_v3,"
  243. "thor_data400::in::flvehreg_01_prethor_upd20020625_v3_flag,"
  244. "thor_data400::in::flvehreg_01_prethor_upd20020715_v3,"
  245. "thor_data400::in::flvehreg_01_prethor_upd20020715_v3_v3_flag,"
  246. "thor_data400::in::flvehreg_01_prethor_upd20020816_v3,"
  247. "thor_data400::in::flvehreg_01_prethor_upd20020816_v3_flag,"
  248. "thor_data400::in::flvehreg_01_prethor_upd20020625_v3,"
  249. "thor_data400::in::fl_lic_prethor_200208v2,"
  250. "thor_data400::in::fl_lic_prethor_200209,"
  251. "thor_data400::in::fl_lic_prethor_200210,"
  252. "thor_data400::in::fl_lic_prethor_200210_reclean,"
  253. "thor_data400::in::fl_lic_upd_200301,"
  254. "thor_data400::in::fl_lic_upd_200302,"
  255. "thor_data400::in::oh_lic_200209,"
  256. "thor_data400::in::ohio_lic_upd_200210,"
  257. "thor_data400::prepped_for_keys,"
  258. "a0e65__w20060224-155748,"
  259. "common_did,"
  260. "test::ftest1,"
  261. "thor_data50::BASE::chunk,"
  262. "hthor::key::codes_v320040901,"
  263. "thor_data400::in::ucc_direct_ok_99999999_event_20060124,"
  264. "thor400::ks_work::distancedetails";
  265. #ifndef COMPAT
  266. void dispFDesc(IFileDescriptor *fdesc)
  267. {
  268. printf("======================================\n");
  269. Owned<IPropertyTree> pt = createPTree("File");
  270. fdesc->serializeTree(*pt);
  271. StringBuffer out;
  272. toXML(pt,out);
  273. printf("%s\n",out.str());
  274. Owned<IFileDescriptor> fdesc2 = deserializeFileDescriptorTree(pt);
  275. toXML(pt,out.clear());
  276. printf("%s\n",out.str());
  277. unsigned np = fdesc->numParts();
  278. unsigned ncl = fdesc->numClusters();
  279. printf("numclusters = %d, numparts=%d\n",ncl,np);
  280. for (unsigned pass=0;pass<1;pass++) {
  281. for (unsigned ip=0;ip<np;ip++) {
  282. IPartDescriptor *part = fdesc->queryPart(ip);
  283. unsigned nc = part->numCopies();
  284. for (unsigned ic=0;ic<nc;ic++) {
  285. StringBuffer tmp1;
  286. StringBuffer tmp2;
  287. StringBuffer tmp3;
  288. StringBuffer tmp4;
  289. RemoteFilename rfn;
  290. bool blocked;
  291. out.clear().appendf("%d,%d: '%s' '%s' '%s' '%s' '%s' '%s' %s%s%s",ip,ic,
  292. part->getDirectory(tmp1,ic).str(),
  293. part->getTail(tmp2).str(),
  294. part->getPath(tmp3,ic).str(),
  295. fdesc->getFilename(ip,ic,rfn).getRemotePath(tmp4).str(), // multi TBD
  296. fdesc->queryPartMask()?fdesc->queryPartMask():"",
  297. fdesc->queryDefaultDir()?fdesc->queryDefaultDir():"",
  298. fdesc->isGrouped()?"GROUPED ":"",
  299. fdesc->queryKind()?fdesc->queryKind():"",
  300. fdesc->isCompressed(&blocked)?(blocked?" BLOCKCOMPRESSED":" COMPRESSED"):""
  301. );
  302. printf("%s\n",out.str());
  303. if (1) {
  304. MemoryBuffer mb;
  305. part->serialize(mb);
  306. Owned<IPartDescriptor> copypart;
  307. copypart.setown(deserializePartFileDescriptor(mb));
  308. StringBuffer out2;
  309. out2.appendf("%d,%d: '%s' '%s' '%s' '%s' '%s' '%s' %s%s%s",ip,ic,
  310. copypart->getDirectory(tmp1.clear(),ic).str(),
  311. copypart->getTail(tmp2.clear()).str(),
  312. copypart->getPath(tmp3.clear(),ic).str(),
  313. copypart->getFilename(ic,rfn).getRemotePath(tmp4.clear()).str(), // multi TBD
  314. copypart->queryOwner().queryPartMask()?copypart->queryOwner().queryPartMask():"",
  315. copypart->queryOwner().queryDefaultDir()?copypart->queryOwner().queryDefaultDir():"",
  316. copypart->queryOwner().isGrouped()?"GROUPED ":"",
  317. copypart->queryOwner().queryKind()?copypart->queryOwner().queryKind():"",
  318. copypart->queryOwner().isCompressed(&blocked)?(blocked?" BLOCKCOMPRESSED":" COMPRESSED"):""
  319. );
  320. if (strcmp(out.str(),out2.str())!=0)
  321. printf("FAILED!\n%s\n%s\n",out.str(),out2.str());
  322. pt.setown(createPTree("File"));
  323. copypart->queryOwner().serializeTree(*pt);
  324. StringBuffer out;
  325. toXML(pt,out);
  326. // printf("%d,%d: \n%s\n",ip,ic,out.str());
  327. }
  328. }
  329. }
  330. }
  331. }
  332. #endif
  333. // ================================================================================== UNIT TESTS
  334. class CDaliTestsStress : public CppUnit::TestFixture
  335. {
  336. CPPUNIT_TEST_SUITE(CDaliTestsStress);
  337. CPPUNIT_TEST(testInit);
  338. CPPUNIT_TEST(testDFS);
  339. // CPPUNIT_TEST(testReadAllSDS); // Ignoring this test; See comments below
  340. CPPUNIT_TEST(testFiles);
  341. #ifndef COMPAT
  342. CPPUNIT_TEST(testDF1);
  343. CPPUNIT_TEST(testDF2);
  344. CPPUNIT_TEST(testMisc);
  345. CPPUNIT_TEST(testDFile);
  346. #endif
  347. CPPUNIT_TEST_SUITE_END();
  348. const IContextLogger &logctx;
  349. public:
  350. CDaliTestsStress() : logctx(queryDummyContextLogger())
  351. {
  352. }
  353. ~CDaliTestsStress()
  354. {
  355. daliClientEnd();
  356. }
  357. void testInit()
  358. {
  359. daliClientInit();
  360. }
  361. #ifndef COMPAT
  362. void testDF1()
  363. {
  364. const char * fname = "testing::propfile2";
  365. Owned<IFileDescriptor> fdesc = createFileDescriptor();
  366. Owned<IPropertyTree> pt = createPTree("Attr");
  367. RemoteFilename rfn;
  368. rfn.setRemotePath("//10.150.10.80/c$/thordata/test/part._1_of_3");
  369. pt->setPropInt("@size",123);
  370. fdesc->setPart(0,rfn,pt);
  371. rfn.setRemotePath("//10.150.10.81/c$/thordata/test/part._2_of_3");
  372. pt->setPropInt("@size",456);
  373. fdesc->setPart(1,rfn,pt);
  374. rfn.setRemotePath("//10.150.10.82/c$/thordata/test/part._3_of_3");
  375. pt->setPropInt("@size",789);
  376. fdesc->setPart(2,rfn,pt);
  377. dispFDesc(fdesc);
  378. try {
  379. removeLogical(fname, user);
  380. Owned<IDistributedFile> file = queryDistributedFileDirectory().createNew(fdesc);
  381. {
  382. DistributedFilePropertyLock lock(file);
  383. lock.queryAttributes().setProp("@testing","1");
  384. }
  385. file->attach(fname,user);
  386. } catch (IException *e) {
  387. StringBuffer msg;
  388. e->errorMessage(msg);
  389. logctx.CTXLOG("Caught exception while setting property: %s", msg.str());
  390. e->Release();
  391. }
  392. }
  393. void testDF2() // 4*3 superfile
  394. {
  395. Owned<IFileDescriptor> fdesc = createFileDescriptor();
  396. Owned<IPropertyTree> pt = createPTree("Attr");
  397. RemoteFilename rfn;
  398. rfn.setRemotePath("//10.150.10.80/c$/thordata/test/partone._1_of_3");
  399. pt->setPropInt("@size",1231);
  400. fdesc->setPart(0,rfn,pt);
  401. rfn.setRemotePath("//10.150.10.80/c$/thordata/test/parttwo._1_of_3");
  402. pt->setPropInt("@size",1232);
  403. fdesc->setPart(1,rfn,pt);
  404. rfn.setRemotePath("//10.150.10.80/c$/thordata/test/partthree._1_of_3");
  405. pt->setPropInt("@size",1233);
  406. fdesc->setPart(2,rfn,pt);
  407. rfn.setRemotePath("//10.150.10.80/c$/thordata/test2/partfour._1_of_3");
  408. pt->setPropInt("@size",1234);
  409. fdesc->setPart(3,rfn,pt);
  410. rfn.setRemotePath("//10.150.10.81/c$/thordata/test/partone._2_of_3");
  411. pt->setPropInt("@size",4565);
  412. fdesc->setPart(4,rfn,pt);
  413. rfn.setRemotePath("//10.150.10.81/c$/thordata/test/parttwo._2_of_3");
  414. pt->setPropInt("@size",4566);
  415. fdesc->setPart(5,rfn,pt);
  416. rfn.setRemotePath("//10.150.10.81/c$/thordata/test/partthree._2_of_3");
  417. pt->setPropInt("@size",4567);
  418. fdesc->setPart(6,rfn,pt);
  419. rfn.setRemotePath("//10.150.10.81/c$/thordata/test2/partfour._2_of_3");
  420. pt->setPropInt("@size",4568);
  421. fdesc->setPart(7,rfn,pt);
  422. rfn.setRemotePath("//10.150.10.82/c$/thordata/test/partone._3_of_3");
  423. pt->setPropInt("@size",7899);
  424. fdesc->setPart(8,rfn,pt);
  425. rfn.setRemotePath("//10.150.10.82/c$/thordata/test/parttwo._3_of_3");
  426. pt->setPropInt("@size",78910);
  427. fdesc->setPart(9,rfn,pt);
  428. rfn.setRemotePath("//10.150.10.82/c$/thordata/test/partthree._3_of_3");
  429. pt->setPropInt("@size",78911);
  430. fdesc->setPart(10,rfn,pt);
  431. rfn.setRemotePath("//10.150.10.82/c$/thordata/test2/partfour._3_of_3");
  432. pt->setPropInt("@size",78912);
  433. fdesc->setPart(11,rfn,pt);
  434. ClusterPartDiskMapSpec mspec;
  435. mspec.interleave = 4;
  436. fdesc->endCluster(mspec);
  437. dispFDesc(fdesc);
  438. }
  439. void testMisc()
  440. {
  441. ClusterPartDiskMapSpec mspec;
  442. Owned<IGroup> grp = createIGroup("10.150.10.1-3");
  443. RemoteFilename rfn;
  444. for (unsigned i=0;i<3;i++)
  445. for (unsigned ic=0;ic<mspec.defaultCopies;ic++) {
  446. constructPartFilename(grp,i+1,3,(i==1)?"test.txt":NULL,"test._$P$_of_$N$","/c$/thordata/test",ic,mspec,rfn);
  447. StringBuffer tmp;
  448. printf("%d,%d: %s\n",i,ic,rfn.getRemotePath(tmp).str());
  449. }
  450. }
  451. void testDFile()
  452. {
  453. ClusterPartDiskMapSpec map;
  454. { // 1: single part file old method
  455. #define TN "1"
  456. removeLogical("test::ftest" TN, user);
  457. Owned<IFileDescriptor> fdesc = createFileDescriptor();
  458. RemoteFilename rfn;
  459. rfn.setRemotePath("//10.150.10.1/c$/thordata/test/ftest" TN "._1_of_1");
  460. fdesc->setPart(0,rfn);
  461. fdesc->endCluster(map);
  462. fdesc->queryPart(0)->queryProperties().setPropInt64("@size", 123);
  463. Owned<IDistributedFile> file = queryDistributedFileDirectory().createNew(fdesc);
  464. file->attach("test::ftest" TN,user);
  465. #undef TN
  466. }
  467. { // 2: single part file new method
  468. #define TN "2"
  469. removeLogical("test::ftest" TN, user);
  470. Owned<IFileDescriptor> fdesc = createFileDescriptor();
  471. fdesc->setPartMask("ftest" TN "._$P$_of_$N$");
  472. fdesc->setNumParts(1);
  473. fdesc->queryPart(0)->queryProperties().setPropInt64("@size", 123);
  474. Owned<IGroup> grp = createIGroup("10.150.10.1");
  475. fdesc->addCluster(grp,map);
  476. Owned<IDistributedFile> file = queryDistributedFileDirectory().createNew(fdesc);
  477. file->attach("test::ftest" TN,user);
  478. #undef TN
  479. }
  480. queryNamedGroupStore().add("__testgroup3__", { "10.150.10.1", "10.150.10.2", "10.150.10.3" },true);
  481. Owned<IGroup> grp3 = queryNamedGroupStore().lookup("test_dummy_group");
  482. { // 3: three parts file old method
  483. #define TN "3"
  484. removeLogical("test::ftest" TN, user);
  485. Owned<IFileDescriptor> fdesc = createFileDescriptor();
  486. RemoteFilename rfn;
  487. rfn.setRemotePath("//10.150.10.1/c$/thordata/test/ftest" TN "._1_of_3");
  488. fdesc->setPart(0,rfn);
  489. rfn.setRemotePath("//10.150.10.2/c$/thordata/test/ftest" TN "._2_of_3");
  490. fdesc->setPart(1,rfn);
  491. rfn.setRemotePath("//10.150.10.3/c$/thordata/test/ftest" TN "._3_of_3");
  492. fdesc->setPart(2,rfn);
  493. fdesc->endCluster(map);
  494. for (unsigned p=0; p<fdesc->numParts(); p++)
  495. fdesc->queryPart(p)->queryProperties().setPropInt64("@size", 10);
  496. Owned<IDistributedFile> file = queryDistributedFileDirectory().createNew(fdesc);
  497. file->attach("test::ftest" TN,user);
  498. #undef TN
  499. }
  500. { // 4: three part file new method
  501. #define TN "4"
  502. removeLogical("test::ftest" TN, user);
  503. Owned<IFileDescriptor> fdesc = createFileDescriptor();
  504. fdesc->setPartMask("ftest" TN "._$P$_of_$N$");
  505. fdesc->setNumParts(3);
  506. for (unsigned p=0; p<fdesc->numParts(); p++)
  507. fdesc->queryPart(p)->queryProperties().setPropInt64("@size", 10);
  508. fdesc->addCluster(grp3,map);
  509. Owned<IDistributedFile> file = queryDistributedFileDirectory().createNew(fdesc);
  510. file->attach("test::ftest" TN,user);
  511. #undef TN
  512. }
  513. }
  514. #endif
  515. /*
  516. * This test is invasive, obsolete and the main source of
  517. * errors in the DFS code. It was created on a time where
  518. * the DFS API was spread open and methods could openly
  519. * fiddle with its internals without injury. Times have changed.
  520. *
  521. * TODO: Convert this test into a proper test of the DFS as
  522. * it currently stands, not work around its deficiencies.
  523. *
  524. * Unfortunately, to do that, some functionality has to be
  525. * re-worked (like creating groups, adding files to it,
  526. * creating physical temporary files, etc).
  527. */
  528. void testDFS()
  529. {
  530. const size32_t recsize = 17;
  531. StringBuffer s;
  532. unsigned i;
  533. unsigned n;
  534. unsigned t;
  535. queryNamedGroupStore().remove("daregress_group");
  536. dir.removeEntry("daregress::superfile1", user);
  537. std::vector<std::string> hosts;
  538. for (n=0;n<400;n++)
  539. {
  540. s.clear().append("192.168.").append(n/256).append('.').append(n%256);
  541. hosts.push_back(s.str());
  542. }
  543. queryNamedGroupStore().add("daregress_group", hosts, true);
  544. Owned<IGroup> group = queryNamedGroupStore().lookup("daregress_group");
  545. ASSERT(queryNamedGroupStore().find(group,s.clear()) && "Created logical group not found");
  546. ASSERT(stricmp(s.str(),"daregress_group")==0 && "Created logical group found with wrong name");
  547. group.setown(queryNamedGroupStore().lookup("daregress_group"));
  548. ASSERT(group && "named group lookup failed");
  549. logctx.CTXLOG("Named group created - 400 nodes");
  550. for (i=0;i<100;i++) {
  551. Owned<IPropertyTree> pp = createPTree("Part");
  552. Owned<IFileDescriptor>fdesc = createFileDescriptor();
  553. fdesc->setDefaultDir("thordata/regress");
  554. n = 9;
  555. for (unsigned k=0;k<400;k++) {
  556. s.clear().append("192.168.").append(n/256).append('.').append(n%256);
  557. Owned<INode> node = createINode(s.str());
  558. pp->setPropInt64("@size",(n*777+i)*recsize);
  559. s.clear().append("daregress_test").append(i).append("._").append(n+1).append("_of_400");
  560. fdesc->setPart(n,node,s.str(),pp);
  561. n = (n+9)%400;
  562. }
  563. fdesc->queryProperties().setPropInt("@recordSize",17);
  564. s.clear().append("daregress::test").append(i);
  565. removeLogical(s.str(), user);
  566. StringBuffer cname;
  567. Owned<IDistributedFile> dfile = dir.createNew(fdesc);
  568. ASSERT(stricmp(dfile->getClusterName(0,cname),"daregress_group")==0 && "Cluster name wrong");
  569. s.clear().append("daregress::test").append(i);
  570. dfile->attach(s.str(),user);
  571. }
  572. logctx.CTXLOG("DFile create done - 100 files");
  573. unsigned samples = 5;
  574. t = 33;
  575. for (i=0;i<100;i++) {
  576. s.clear().append("daregress::test").append(t);
  577. ASSERT(dir.exists(s.str(),user) && "Could not find sub-file");
  578. Owned<IDistributedFile> dfile = dir.lookup(s.str(), user, AccessMode::tbdRead, false, false, nullptr, false);
  579. ASSERT(dfile && "Could not find sub-file");
  580. offset_t totsz = 0;
  581. n = 11;
  582. for (unsigned k=0;k<400;k++) {
  583. Owned<IDistributedFilePart> part = dfile->getPart(n);
  584. ASSERT(part && "part not found");
  585. s.clear().append("192.168.").append(n/256).append('.').append(n%256);
  586. Owned<INode> node = createINode(s.str());
  587. ASSERT(node->equals(part->queryNode()) && "part node mismatch");
  588. ASSERT(part->getFileSize(false,false)==(n*777+t)*recsize && "size node mismatch");
  589. s.clear().append("daregress_test").append(t).append("._").append(n+1).append("_of_400");
  590. /* ** TBD
  591. if (stricmp(s.str(),part->queryPartName())!=0)
  592. ERROR4("part name mismatch %d, %d '%s' '%s'",t,n,s.str(),part->queryPartName());
  593. */
  594. totsz += (n*777+t)*recsize;
  595. if ((samples>0)&&(i+n+t==k)) {
  596. samples--;
  597. RemoteFilename rfn;
  598. part->getFilename(rfn,samples%2);
  599. StringBuffer fn;
  600. rfn.getRemotePath(fn);
  601. logctx.CTXLOG("SAMPLE: %d,%d %s",t,n,fn.str());
  602. }
  603. n = (n+11)%400;
  604. }
  605. ASSERT(totsz==dfile->getFileSize(false,false) && "total size mismatch");
  606. t = (t+33)%100;
  607. }
  608. logctx.CTXLOG("DFile lookup done - 100 files");
  609. // check iteration
  610. __int64 crctot = 0;
  611. unsigned np = 0;
  612. unsigned totrows = 0;
  613. Owned<IDistributedFileIterator> fiter = dir.getIterator("daregress::*",false, user, defaultNonPrivilegedUser);
  614. Owned<IDistributedFilePartIterator> piter;
  615. ForEach(*fiter) {
  616. piter.setown(fiter->query().getIterator());
  617. ForEach(*piter) {
  618. RemoteFilename rfn;
  619. StringBuffer s;
  620. piter->query().getFilename(rfn,0);
  621. rfn.getRemotePath(s);
  622. piter->query().getFilename(rfn,1);
  623. rfn.getRemotePath(s);
  624. crctot += crc32(s.str(),s.length(),0);
  625. np++;
  626. totrows += (unsigned)(piter->query().getFileSize(false,false)/fiter->query().queryAttributes().getPropInt("@recordSize",-1));
  627. }
  628. }
  629. piter.clear();
  630. fiter.clear();
  631. logctx.CTXLOG("DFile iterate done - %d parts, %d rows, CRC sum %" I64F "d",np,totrows,crctot);
  632. Owned<IDistributedSuperFile> sfile;
  633. sfile.setown(dir.createSuperFile("daregress::superfile1",user,true,false));
  634. for (i = 0;i<100;i++) {
  635. s.clear().append("daregress::test").append(i);
  636. sfile->addSubFile(s.str());
  637. }
  638. sfile.clear();
  639. sfile.setown(dir.lookupSuperFile("daregress::superfile1", user));
  640. ASSERT(sfile && "Could not find added superfile");
  641. __int64 savcrc = crctot;
  642. crctot = 0;
  643. np = 0;
  644. totrows = 0;
  645. size32_t srs = (size32_t)sfile->queryAttributes().getPropInt("@recordSize",-1);
  646. ASSERT(srs==17 && "Superfile does not match subfile row size");
  647. piter.setown(sfile->getIterator());
  648. ForEach(*piter) {
  649. RemoteFilename rfn;
  650. StringBuffer s;
  651. piter->query().getFilename(rfn,0);
  652. rfn.getRemotePath(s);
  653. piter->query().getFilename(rfn,1);
  654. rfn.getRemotePath(s);
  655. crctot += crc32(s.str(),s.length(),0);
  656. np++;
  657. totrows += (unsigned)(piter->query().getFileSize(false,false)/srs);
  658. }
  659. piter.clear();
  660. logctx.CTXLOG("Superfile iterate done - %d parts, %d rows, CRC sum %" I64F "d",np,totrows,crctot);
  661. ASSERT(crctot==savcrc && "SuperFile does not match sub files");
  662. unsigned tr = (unsigned)(sfile->getFileSize(false,false)/srs);
  663. ASSERT(totrows==tr && "Superfile size does not match part sum");
  664. sfile->detach();
  665. sfile.clear();
  666. sfile.setown(dir.lookupSuperFile("daregress::superfile1",user));
  667. ASSERT(!sfile && "Superfile deletion failed");
  668. t = 37;
  669. for (i=0;i<100;i++) {
  670. s.clear().append("daregress::test").append(t);
  671. removeLogical(s.str(), user);
  672. t = (t+37)%100;
  673. }
  674. logctx.CTXLOG("DFile removal complete");
  675. t = 39;
  676. for (i=0;i<100;i++) {
  677. ASSERT(!dir.exists(s.str(),user) && "Found dir after deletion");
  678. Owned<IDistributedFile> dfile = dir.lookup(s.str(), user, AccessMode::tbdRead, false, false, nullptr, false);
  679. ASSERT(!dfile && "Found file after deletion");
  680. t = (t+39)%100;
  681. }
  682. logctx.CTXLOG("DFile removal check complete");
  683. queryNamedGroupStore().remove("daregress_group");
  684. ASSERT(!queryNamedGroupStore().lookup("daregress_group") && "Named group not removed");
  685. }
  686. void testFiles()
  687. {
  688. StringBuffer fn;
  689. const char *s = filelist;
  690. unsigned slowest = 0;
  691. StringAttr slowname;
  692. unsigned tot = 0;
  693. unsigned n = 0;
  694. while (*s) {
  695. fn.clear();
  696. while (*s==',')
  697. s++;
  698. while (*s&&(*s!=','))
  699. fn.append(*(s++));
  700. if (fn.length()) {
  701. n++;
  702. unsigned ss = msTick();
  703. checkFiles(fn);
  704. unsigned t = (msTick()-ss);
  705. if (t>slowest) {
  706. slowest = t;
  707. slowname.set(fn);
  708. }
  709. tot += t;
  710. }
  711. }
  712. printf("Complete in %ds avg %dms\n",tot/1000,tot/(n?n:1));
  713. if (!slowname.isEmpty())
  714. printf("Slowest %s = %dms\n",slowname.get(),slowest);
  715. }
  716. void testReadBranch(const char *path)
  717. {
  718. PROGLOG("Connecting to %s",path);
  719. Owned<IRemoteConnection> conn = querySDS().connect(path, myProcessSession(), RTM_LOCK_READ, 10000);
  720. ASSERT(conn && "Could not connect");
  721. IPropertyTree *root = conn->queryRoot();
  722. Owned<IAttributeIterator> aiter = root->getAttributes();
  723. StringBuffer s;
  724. ForEach(*aiter)
  725. aiter->getValue(s.clear());
  726. aiter.clear();
  727. root->getProp(NULL,s.clear());
  728. Owned<IPropertyTreeIterator> iter = root->getElements("*");
  729. StringAttrArray children;
  730. UnsignedArray childidx;
  731. ForEach(*iter) {
  732. children.append(*new StringAttrItem(iter->query().queryName()));
  733. childidx.append(root->queryChildIndex(&iter->query()));
  734. }
  735. iter.clear();
  736. conn.clear();
  737. ForEachItemIn(i,children) {
  738. s.clear().append(path);
  739. if (path[strlen(path)-1]!='/')
  740. s.append('/');
  741. s.append(children.item(i).text).append('[').append(childidx.item(i)+1).append(']');
  742. testReadBranch(s.str());
  743. }
  744. }
  745. /*
  746. * This test is silly and can take a very long time on clusters with
  747. * a large file-system. But keeping it here for further reference.
  748. * MORE: Maybe, this could be added to daliadmin or a thorough check
  749. * on the filesystem, together with super-file checks et al.
  750. void testReadAllSDS()
  751. {
  752. logctx.CTXLOG("Test SDS connecting to every branch");
  753. testReadBranch("/");
  754. logctx.CTXLOG("Connected to every branch");
  755. }
  756. */
  757. };
  758. CPPUNIT_TEST_SUITE_REGISTRATION( CDaliTestsStress );
  759. CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( CDaliTestsStress, "CDaliTestsStress" );
  760. // ================================================================================== UNIT TESTS
  761. class CDaliSDSStressTests : public CppUnit::TestFixture
  762. {
  763. CPPUNIT_TEST_SUITE(CDaliSDSStressTests);
  764. CPPUNIT_TEST(testInit);
  765. CPPUNIT_TEST(testSDSRW);
  766. CPPUNIT_TEST(testSDSSubs);
  767. CPPUNIT_TEST(testSDSSubs2);
  768. CPPUNIT_TEST(testSDSNodeSubs);
  769. CPPUNIT_TEST(testEphemeralLocks);
  770. CPPUNIT_TEST(testSiblingPerfLocal);
  771. CPPUNIT_TEST(testSiblingPerfDali);
  772. CPPUNIT_TEST(testSiblingPerfContention);
  773. CPPUNIT_TEST_SUITE_END();
  774. const IContextLogger &logctx;
  775. static const unsigned numCChangeTests = 1;
  776. static const unsigned subsPerCChange = 10;
  777. static const unsigned numCChangeCommits = 10;
  778. void sdsNodeCommit(const char *test, unsigned from, unsigned to, bool finalDelete)
  779. {
  780. class CNodeSubCommitThread : public CInterface, implements IThreaded
  781. {
  782. StringAttr xpath;
  783. bool finalDelete;
  784. CThreaded threaded;
  785. public:
  786. IMPLEMENT_IINTERFACE;
  787. CNodeSubCommitThread(const char *_xpath, bool _finalDelete) : threaded("CNodeSubCommitThread"), xpath(_xpath), finalDelete(_finalDelete)
  788. {
  789. }
  790. virtual void threadmain() override
  791. {
  792. unsigned mode = RTM_LOCK_WRITE;
  793. if (finalDelete)
  794. mode |= RTM_DELETE_ON_DISCONNECT;
  795. Owned<IRemoteConnection> conn = querySDS().connect(xpath, myProcessSession(), mode, 1000000);
  796. assertex(conn);
  797. for (unsigned i=0; i<5; i++)
  798. {
  799. VStringBuffer val("newval%d", i+1);
  800. conn->queryRoot()->setProp(NULL, val.str());
  801. conn->commit();
  802. }
  803. conn->queryRoot()->setProp("subnode", "newval");
  804. conn->commit();
  805. conn.clear(); // if finalDelete=true, deletes subscribed node in process, should get notificaiton
  806. }
  807. void start()
  808. {
  809. threaded.init(this);
  810. }
  811. void join()
  812. {
  813. threaded.join();
  814. }
  815. };
  816. CIArrayOf<CNodeSubCommitThread> commitThreads;
  817. for (unsigned i=from; i<=to; i++)
  818. {
  819. VStringBuffer path("/DAREGRESS/NodeSubTest/node%d", i);
  820. CNodeSubCommitThread *commitThread = new CNodeSubCommitThread(path, finalDelete);
  821. commitThreads.append(* commitThread);
  822. }
  823. ForEachItemIn(t, commitThreads)
  824. commitThreads.item(t).start();
  825. ForEachItemIn(t2, commitThreads)
  826. commitThreads.item(t2).join();
  827. }
  828. unsigned fn(unsigned n, unsigned m, unsigned seed, unsigned depth, IPropertyTree *parent)
  829. {
  830. __int64 val = parent->getPropInt64("val",0);
  831. parent->setPropInt64("val",n+val);
  832. val = parent->getPropInt64("@val",0);
  833. parent->setPropInt64("@val",m+val);
  834. val = parent->getPropInt64(NULL,0);
  835. parent->setPropInt64(NULL,seed+val);
  836. if (Rconn&&((n+m+seed)%100==0))
  837. Rconn->commit();
  838. if (!seed)
  839. return m+n;
  840. if (n==m)
  841. return seed;
  842. if (depth>10)
  843. return seed+n+m;
  844. if (seed%7==n%7)
  845. return n;
  846. if (seed%7==m%7)
  847. return m;
  848. char name[64];
  849. unsigned v = seed;
  850. name[0] = 's';
  851. name[1] = 'u';
  852. name[2] = 'b';
  853. unsigned i = 3;
  854. while (v) {
  855. name[i++] = ('A'+v%26 );
  856. v /= 26;
  857. }
  858. name[i] = 0;
  859. IPropertyTree *child = parent->queryPropTree(name);
  860. if (!child)
  861. child = parent->addPropTree(name, createPTree(name));
  862. return fn(fn(n,seed,seed*17+11,depth+1,child),fn(seed,m,seed*11+17,depth+1,child),seed*19+7,depth+1,child);
  863. }
  864. unsigned fn2(unsigned n, unsigned m, unsigned seed, unsigned depth, StringBuffer &parentname)
  865. {
  866. if (!Rconn)
  867. return 0;
  868. if ((n+m+seed)%25==0) {
  869. Rconn->commit();
  870. Rconn->Release();
  871. Rconn = querySDS().connect("/DAREGRESS",myProcessSession(), 0, 1000000);
  872. ASSERT(Rconn && "Failed to connect to /DAREGRESS");
  873. }
  874. IPropertyTree *parent = parentname.length()?Rconn->queryRoot()->queryPropTree(parentname.str()):Rconn->queryRoot();
  875. ASSERT(parent && "Failed to connect to parent");
  876. __int64 val = parent->getPropInt64("val",0);
  877. parent->setPropInt64("val",n+val);
  878. val = parent->getPropInt64("@val",0);
  879. parent->setPropInt64("@val",m+val);
  880. val = parent->getPropInt64(NULL,0);
  881. parent->setPropInt64(NULL,seed+val);
  882. if (!seed)
  883. return m+n;
  884. if (n==m)
  885. return seed;
  886. if (depth>10)
  887. return seed+n+m;
  888. if (seed%7==n%7)
  889. return n;
  890. if (seed%7==m%7)
  891. return m;
  892. char name[64];
  893. unsigned v = seed;
  894. name[0] = 's';
  895. name[1] = 'u';
  896. name[2] = 'b';
  897. unsigned i = 3;
  898. while (v) {
  899. name[i++] = ('A'+v%26 );
  900. v /= 26;
  901. }
  902. name[i] = 0;
  903. unsigned l = parentname.length();
  904. if (parentname.length())
  905. parentname.append('/');
  906. parentname.append(name);
  907. IPropertyTree *child = parent->queryPropTree(name);
  908. if (!child)
  909. child = parent->addPropTree(name, createPTree(name));
  910. unsigned ret = fn2(fn2(n,seed,seed*17+11,depth+1,parentname),fn2(seed,m,seed*11+17,depth+1,parentname),seed*19+7,depth+1,parentname);
  911. parentname.setLength(l);
  912. return ret;
  913. }
  914. public:
  915. CDaliSDSStressTests() : logctx(queryDummyContextLogger())
  916. {
  917. }
  918. ~CDaliSDSStressTests()
  919. {
  920. daliClientEnd();
  921. }
  922. void testInit()
  923. {
  924. daliClientInit();
  925. }
  926. void testSDSRW()
  927. {
  928. Owned<IPropertyTree> ref = createPTree("DAREGRESS");
  929. fn(1,2,3,0,ref);
  930. StringBuffer refstr;
  931. toXML(ref,refstr,0,XML_SortTags|XML_Format);
  932. logctx.CTXLOG("Created reference size %d",refstr.length());
  933. Owned<IRemoteConnection> conn = querySDS().connect("/DAREGRESS",myProcessSession(), RTM_CREATE, 1000000);
  934. Rconn = conn;
  935. IPropertyTree *root = conn->queryRoot();
  936. fn(1,2,3,0,root);
  937. conn.clear();
  938. logctx.CTXLOG("Created test branch 1");
  939. conn.setown(querySDS().connect("/DAREGRESS",myProcessSession(), RTM_DELETE_ON_DISCONNECT, 1000000));
  940. root = conn->queryRoot();
  941. StringBuffer s;
  942. toXML(root,s,0,XML_SortTags|XML_Format);
  943. ASSERT(strcmp(s.str(),refstr.str())==0 && "Branch 1 does not match");
  944. conn.clear();
  945. conn.setown(querySDS().connect("/DAREGRESS",myProcessSession(), 0, 1000000));
  946. ASSERT(!conn && "RTM_DELETE_ON_DISCONNECT failed");
  947. Rconn = querySDS().connect("/DAREGRESS",myProcessSession(), RTM_CREATE, 1000000);
  948. StringBuffer pn;
  949. fn2(1,2,3,0,pn);
  950. ::Release(Rconn);
  951. logctx.CTXLOG("Created test branch 2");
  952. Rconn = NULL;
  953. conn.setown(querySDS().connect("/DAREGRESS",myProcessSession(), RTM_DELETE_ON_DISCONNECT, 1000000));
  954. root = conn->queryRoot();
  955. toXML(root,s.clear(),0,XML_SortTags|XML_Format);
  956. ASSERT(strcmp(s.str(),refstr.str())==0 && "Branch 2 does not match");
  957. conn.clear();
  958. conn.setown(querySDS().connect("/DAREGRESS",myProcessSession(), 0, 1000000));
  959. ASSERT(!conn && "RTM_DELETE_ON_DISCONNECT failed");
  960. }
  961. void testSDSSubs()
  962. {
  963. class CChange : public Thread
  964. {
  965. class CCSub : public CInterface, implements ISDSConnectionSubscription, implements ISDSSubscription
  966. {
  967. unsigned n;
  968. unsigned &count;
  969. public:
  970. IMPLEMENT_IINTERFACE;
  971. CCSub(unsigned _n,unsigned &_count)
  972. : count(_count)
  973. {
  974. n = _n;
  975. }
  976. virtual void notify()
  977. {
  978. CriticalBlock block(subchangesect);
  979. subchangetotal += n;
  980. subchangenum++;
  981. count++;
  982. }
  983. virtual void notify(SubscriptionId id, const char *xpath, SDSNotifyFlags flags, unsigned valueLen, const void *valueData)
  984. {
  985. CriticalBlock block(subchangesect);
  986. subchangetotal += n;
  987. subchangenum++;
  988. subchangetotal += (unsigned)flags;
  989. subchangetotal += crc32(xpath,strlen(xpath),0);
  990. if (valueLen)
  991. subchangetotal += crc32((const char *)valueData,valueLen,0);
  992. count++;
  993. }
  994. };
  995. Owned<IRemoteConnection> conn;
  996. StringAttr path;
  997. SubscriptionId id[10];
  998. unsigned n;
  999. unsigned count;
  1000. public:
  1001. Semaphore stopsem;
  1002. CChange(unsigned _n)
  1003. {
  1004. n = _n;
  1005. StringBuffer s("/DAREGRESS/CONSUB");
  1006. s.append(n+1);
  1007. path.set(s.str());
  1008. conn.setown(querySDS().connect(path, myProcessSession(), RTM_CREATE|RTM_DELETE_ON_DISCONNECT, 1000000));
  1009. unsigned i;
  1010. for (i=0;i<subsPerCChange/2;i++)
  1011. {
  1012. Owned<CCSub> sub = new CCSub(n*subsPerCChange+i,count);
  1013. id[i] = conn->subscribe(*sub);
  1014. }
  1015. s.append("/testprop");
  1016. for (;i<subsPerCChange;i++)
  1017. {
  1018. Owned<CCSub> sub = new CCSub(n*subsPerCChange+i,count);
  1019. id[i] = querySDS().subscribe(s.str(),*sub,false,true);
  1020. }
  1021. count = 0;
  1022. start();
  1023. }
  1024. virtual int run()
  1025. {
  1026. unsigned i;
  1027. for (i = 0;i<numCChangeCommits; i++) {
  1028. conn->queryRoot()->setPropInt("testprop", (i*17+n*21)%100);
  1029. conn->commit();
  1030. for (unsigned j=0;j<1000;j++) {
  1031. {
  1032. CriticalBlock block(subchangesect);
  1033. if (count>=(i+1)*10)
  1034. break;
  1035. }
  1036. Sleep(10);
  1037. }
  1038. }
  1039. stopsem.wait();
  1040. for (i=0;i<subsPerCChange/2;i++)
  1041. conn->unsubscribe(id[i]);
  1042. for (;i<subsPerCChange;i++)
  1043. querySDS().unsubscribe(id[i]);
  1044. return 0;
  1045. }
  1046. };
  1047. subchangenum = 0;
  1048. subchangetotal = 0;
  1049. IArrayOf<CChange> a;
  1050. for (unsigned i=0; i<numCChangeTests ; i++)
  1051. a.append(*new CChange(i));
  1052. unsigned last = 0;
  1053. for (;;)
  1054. {
  1055. Sleep(1000);
  1056. {
  1057. CriticalBlock block(subchangesect);
  1058. if (subchangenum==last)
  1059. break;
  1060. last = subchangenum;
  1061. }
  1062. }
  1063. ForEachItemIn(i1, a)
  1064. a.item(i1).stopsem.signal();
  1065. ForEachItemIn(i2, a)
  1066. a.item(i2).join();
  1067. logctx.CTXLOG("%d subscription notifications, check sum = %" I64F "d",subchangenum,subchangetotal);
  1068. ASSERT(subchangenum==( numCChangeTests * subsPerCChange * numCChangeCommits) && "Not all notifications received");
  1069. }
  1070. void testSDSSubs2()
  1071. {
  1072. class CResult
  1073. {
  1074. Semaphore sem;
  1075. StringBuffer resultString;
  1076. CriticalSection crit;
  1077. public:
  1078. CResult()
  1079. {
  1080. }
  1081. void add(const char *message)
  1082. {
  1083. CriticalBlock b(crit);
  1084. if (resultString.length())
  1085. resultString.append("|");
  1086. resultString.append(message);
  1087. sem.signal();
  1088. }
  1089. Semaphore &querySem() { return sem; }
  1090. void clear() { resultString.clear(); }
  1091. bool wait(unsigned numExpected)
  1092. {
  1093. for (unsigned t=0; t<numExpected; t++)
  1094. {
  1095. if (!sem.wait(5000))
  1096. return false;
  1097. }
  1098. return true;
  1099. }
  1100. StringBuffer &getResultsClear(StringBuffer &ret)
  1101. {
  1102. StringArray array;
  1103. array.appendList(resultString, "|");
  1104. resultString.clear();
  1105. array.sortAscii();
  1106. ForEachItemIn(r, array)
  1107. {
  1108. if (ret.length())
  1109. ret.append("|");
  1110. ret.append(array.item(r));
  1111. }
  1112. return ret;
  1113. }
  1114. } result;
  1115. class CSubscriberContainer : public CInterface
  1116. {
  1117. class CSubscriber : public CSimpleInterfaceOf<ISDSSubscription>
  1118. {
  1119. StringAttr xpath;
  1120. bool sub;
  1121. CResult &result;
  1122. public:
  1123. CSubscriber(CResult &_result, const char *_xpath, bool _sub) : result(_result), xpath(_xpath), sub(_sub)
  1124. {
  1125. }
  1126. virtual void notify(SubscriptionId id, const char *_xpath, SDSNotifyFlags flags, unsigned valueLen, const void *valueData)
  1127. {
  1128. PROGLOG("CSubscriber notified path=%s for subscriber=%s, sub=%s", _xpath, xpath.get(), sub?"true":"false");
  1129. StringBuffer message(xpath);
  1130. if (!sub && valueLen)
  1131. message.append(",").append(valueLen, (const char *)valueData);
  1132. result.add(message);
  1133. }
  1134. };
  1135. Owned<CSubscriber> subscriber;
  1136. SubscriptionId id;
  1137. public:
  1138. CSubscriberContainer(CResult &result, const char *xpath, bool sub)
  1139. {
  1140. subscriber.setown(new CSubscriber(result, xpath, sub));
  1141. id = querySDS().subscribe(xpath, *subscriber, sub, !sub);
  1142. PROGLOG("Subscribed to %s", xpath);
  1143. }
  1144. virtual void beforeDispose() override
  1145. {
  1146. querySDS().unsubscribe(id);
  1147. }
  1148. };
  1149. Owned<IRemoteConnection> conn = querySDS().connect("/", myProcessSession(), RTM_LOCK_WRITE, INFINITE);
  1150. IPropertyTree *root = conn->queryRoot();
  1151. IPropertyTree *daRegress = root->setPropTree("DAREGRESS");
  1152. Owned<IPropertyTree> tree = createPTreeFromXMLString("<TestSub2><a><b1><c/></b1><b2/><b3><d><e/></d></b3><b4><f/></b4></a></TestSub2>");
  1153. daRegress->setPropTree("TestSub2", tree.getClear());
  1154. conn->commit();
  1155. Owned<CSubscriberContainer> sub1 = new CSubscriberContainer(result, "/DAREGRESS/TestSub2/a", true);
  1156. Owned<CSubscriberContainer> sub2 = new CSubscriberContainer(result, "/DAREGRESS/TestSub2/a/b1", false);
  1157. Owned<CSubscriberContainer> sub3 = new CSubscriberContainer(result, "/DAREGRESS/TestSub2/a/b2", false);
  1158. Owned<CSubscriberContainer> sub4 = new CSubscriberContainer(result, "/DAREGRESS/TestSub2/a/b1/c", false);
  1159. Owned<CSubscriberContainer> sub5 = new CSubscriberContainer(result, "/DAREGRESS/TestSub2/a/b3", true);
  1160. Owned<CSubscriberContainer> sub6 = new CSubscriberContainer(result, "/DAREGRESS/TestSub2/a/b4", false);
  1161. Owned<CSubscriberContainer> sub7 = new CSubscriberContainer(result, "/DAREGRESS/TestSub2/a/b4/sub", false);
  1162. StringArray expectedResults;
  1163. expectedResults.append("/DAREGRESS/TestSub2/a");
  1164. expectedResults.append("/DAREGRESS/TestSub2/a|/DAREGRESS/TestSub2/a/b1,testv");
  1165. expectedResults.append("/DAREGRESS/TestSub2/a|/DAREGRESS/TestSub2/a/b2,testv");
  1166. expectedResults.append("/DAREGRESS/TestSub2/a|/DAREGRESS/TestSub2/a/b1/c,testv");
  1167. expectedResults.append("/DAREGRESS/TestSub2/a|/DAREGRESS/TestSub2/a/b1,testv");
  1168. expectedResults.append("/DAREGRESS/TestSub2/a|/DAREGRESS/TestSub2/a/b2,testv");
  1169. expectedResults.append("/DAREGRESS/TestSub2/a");
  1170. expectedResults.append("/DAREGRESS/TestSub2/a|/DAREGRESS/TestSub2/a/b3");
  1171. expectedResults.append("/DAREGRESS/TestSub2/a|/DAREGRESS/TestSub2/a/b3");
  1172. expectedResults.append("/DAREGRESS/TestSub2/a|/DAREGRESS/TestSub2/a/b4,b4value|/DAREGRESS/TestSub2/a/b4/sub,subvalue");
  1173. StringArray props;
  1174. props.appendList("S:TestSub2/a,S:TestSub2/a/b1,S:TestSub2/a/b2,S:TestSub2/a/b1/c,S:TestSub2/a/b1/d,S:TestSub2/a/b2/e,S:TestSub2/a/b2/e/f,D:TestSub2/a/b3/d/e,D:TestSub2/a/b3/d,R:TestSub2/a/b4", ",");
  1175. assertex(expectedResults.ordinality() == props.ordinality());
  1176. ForEachItemIn(p, props)
  1177. {
  1178. const char *cmd = props.item(p);
  1179. const char *propPath=cmd+2;
  1180. switch (*cmd)
  1181. {
  1182. case 'S':
  1183. {
  1184. PROGLOG("Changing %s", propPath);
  1185. daRegress->setProp(propPath, "testv");
  1186. break;
  1187. }
  1188. case 'D':
  1189. {
  1190. PROGLOG("Deleting %s", propPath);
  1191. daRegress->removeProp(propPath);
  1192. break;
  1193. }
  1194. case 'R':
  1195. {
  1196. PROGLOG("Replacing tree %s", propPath);
  1197. Owned<IPropertyTree> tree = createPTreeFromXMLString("<b4><sub><g/>subvalue</sub>b4value</b4>");
  1198. daRegress->setPropTree(propPath, tree.getClear());
  1199. break;
  1200. }
  1201. default:
  1202. throwUnexpected();
  1203. }
  1204. conn->commit();
  1205. const char *expectedResult = expectedResults.item(p);
  1206. StringArray expectedResultArray;
  1207. expectedResultArray.appendList(expectedResult, "|"); // just to get #
  1208. if (!result.wait(expectedResultArray.ordinality()))
  1209. {
  1210. VStringBuffer errMsg("Timeout waiting for subcription notificaitons, where expected result = '%s',", expectedResult);
  1211. CPPUNIT_ASSERT_MESSAGE(errMsg.str(), 0);
  1212. }
  1213. StringBuffer results;
  1214. result.getResultsClear(results);
  1215. PROGLOG("Checking results");
  1216. if (0 == strcmp(expectedResult, results))
  1217. PROGLOG("testSDSSubs2 [ %s ]: MATCH", cmd);
  1218. else
  1219. {
  1220. VStringBuffer errMsg("testSDSSubs2 [ %s ]: MISMATCH", cmd);
  1221. errMsg.newline().append("Expected: ").append(expectedResult);
  1222. errMsg.newline().append("Got: ").append(results);
  1223. PROGLOG("%s", errMsg.str());
  1224. CPPUNIT_ASSERT_MESSAGE(errMsg.str(), 0);
  1225. }
  1226. }
  1227. }
  1228. void testSDSNodeSubs()
  1229. {
  1230. class CResults
  1231. {
  1232. StringArray results;
  1233. CRC32 crc;
  1234. CriticalSection crit;
  1235. public:
  1236. void add(const char *out)
  1237. {
  1238. PROGLOG("%s", out);
  1239. CriticalBlock b(crit); // notify() and therefore add() can be called on multiple threads
  1240. results.append(out);
  1241. }
  1242. unsigned getCRC()
  1243. {
  1244. results.sortAscii();
  1245. ForEachItemIn(r, results)
  1246. {
  1247. const char *result = results.item(r);
  1248. crc.tally(strlen(result), result);
  1249. }
  1250. PROGLOG("CRC = %x", crc.get());
  1251. results.kill();
  1252. return crc.get();
  1253. }
  1254. };
  1255. class CSubscriber : CSimpleInterface, implements ISDSNodeSubscription
  1256. {
  1257. StringAttr path;
  1258. CResults &results;
  1259. unsigned expectedNotifications;
  1260. std::atomic<unsigned> notifications = {0};
  1261. Semaphore joinSem;
  1262. public:
  1263. IMPLEMENT_IINTERFACE_USING(CSimpleInterface);
  1264. CSubscriber(const char *_path, CResults &_results, unsigned _expectedNotifications)
  1265. : path(_path), results(_results), expectedNotifications(_expectedNotifications)
  1266. {
  1267. if (0 == expectedNotifications)
  1268. joinSem.signal();
  1269. }
  1270. virtual void notify(SubscriptionId id, SDSNotifyFlags flags, unsigned valueLen, const void *valueData)
  1271. {
  1272. StringAttr value;
  1273. if (valueLen)
  1274. value.set((const char *)valueData, valueLen);
  1275. VStringBuffer res("Subscriber(%s): flags=%d, value=%s", path.get(), flags, 0==valueLen ? "(none)" : value.get());
  1276. results.add(res);
  1277. if (++notifications == expectedNotifications)
  1278. joinSem.signal();
  1279. }
  1280. void join()
  1281. {
  1282. if (joinSem.wait(5000))
  1283. {
  1284. MilliSleep(100); // wait a bit, see if get more than expected
  1285. unsigned n = notifications;
  1286. if (n == expectedNotifications)
  1287. {
  1288. VStringBuffer out("Subscriber(%s): %d notifications received", path.get(), n);
  1289. results.add(out);
  1290. return;
  1291. }
  1292. }
  1293. VStringBuffer out("Expected %d notifications, received %d", expectedNotifications, notifications.load());
  1294. results.add(out);
  1295. }
  1296. };
  1297. // setup
  1298. Owned<IRemoteConnection> conn = querySDS().connect("/DAREGRESS/NodeSubTest", myProcessSession(), RTM_CREATE, 1000000);
  1299. IPropertyTree *root = conn->queryRoot();
  1300. unsigned i, ai;
  1301. for (i=0; i<10; i++)
  1302. {
  1303. VStringBuffer name("node%d", i+1);
  1304. IPropertyTree *sub = root->setPropTree(name, createPTree());
  1305. for (ai=0; ai<2; ai++)
  1306. {
  1307. VStringBuffer name("@attr%d", i+1);
  1308. VStringBuffer val("val%d", i+1);
  1309. sub->setProp(name, val);
  1310. }
  1311. }
  1312. conn.clear();
  1313. CResults results;
  1314. {
  1315. const char *testPath = "/DAREGRESS/NodeSubTest/doesnotexist";
  1316. Owned<CSubscriber> subscriber = new CSubscriber(testPath, results, 0);
  1317. try
  1318. {
  1319. querySDS().subscribeExact(testPath, *subscriber, true);
  1320. throwUnexpected();
  1321. }
  1322. catch(IException *e)
  1323. {
  1324. if (SDSExcpt_SubscriptionNoMatch != e->errorCode())
  1325. throw;
  1326. results.add("Correctly failed to add subscriber to non-existent node.");
  1327. }
  1328. subscriber.clear();
  1329. }
  1330. {
  1331. const char *testPath = "/DAREGRESS/NodeSubTest/node1";
  1332. Owned<CSubscriber> subscriber = new CSubscriber(testPath, results, 2*5+1+1);
  1333. SubscriptionId id = querySDS().subscribeExact(testPath, *subscriber, false);
  1334. sdsNodeCommit(testPath, 1, 1, false);
  1335. sdsNodeCommit(testPath, 1, 1, true); // will delete 'node1'
  1336. subscriber->join();
  1337. querySDS().unsubscribeExact(id); // will actually be a NOP, as will be already unsubscribed when 'node1' deleted.
  1338. }
  1339. {
  1340. const char *testPath = "/DAREGRESS/NodeSubTest/node*";
  1341. Owned<CSubscriber> subscriber = new CSubscriber(testPath, results, 9*6);
  1342. SubscriptionId id = querySDS().subscribeExact(testPath, *subscriber, false);
  1343. sdsNodeCommit(testPath, 2, 10, false);
  1344. subscriber->join();
  1345. querySDS().unsubscribeExact(id);
  1346. }
  1347. {
  1348. UInt64Array subscriberIds;
  1349. IArrayOf<CSubscriber> subscribers;
  1350. for (i=2; i<=10; i++) // NB: from 2, as 'node1' deleted in previous tests
  1351. {
  1352. for (ai=0; ai<2; ai++)
  1353. {
  1354. VStringBuffer path("/DAREGRESS/NodeSubTest/node%d[@attr%d=\"val%d\"]", i, i, i);
  1355. Owned<CSubscriber> subscriber = new CSubscriber(path, results, 11);
  1356. SubscriptionId id = querySDS().subscribeExact(path, *subscriber, 0==ai);
  1357. subscribers.append(* subscriber.getClear());
  1358. subscriberIds.append(id);
  1359. }
  1360. }
  1361. const char *testPath = "/DAREGRESS/NodeSubTest/node*";
  1362. Owned<CSubscriber> subscriber = new CSubscriber(testPath, results, 9*5+9*(5+1));
  1363. SubscriptionId id = querySDS().subscribeExact(testPath, *subscriber, false);
  1364. sdsNodeCommit(testPath, 2, 10, false);
  1365. sdsNodeCommit(testPath, 2, 10, true);
  1366. subscriber->join();
  1367. querySDS().unsubscribeExact(id);
  1368. ForEachItemIn(s, subscriberIds)
  1369. {
  1370. subscribers.item(s).join();
  1371. querySDS().unsubscribeExact(subscriberIds.item(s));
  1372. }
  1373. }
  1374. ASSERT(0xa68e2324 == results.getCRC() && "SDS Node notifcation differences");
  1375. }
  1376. void testEphemeralLocks()
  1377. {
  1378. auto createEphemeralLock = [&](const char *xpath, unsigned timeout, unsigned type)
  1379. {
  1380. unsigned mode = RTM_LOCK_WRITE | RTM_CREATE_QUERY | RTM_DELETE_ON_DISCONNECT;
  1381. Owned<IRemoteConnection> conn = querySDS().connect(xpath, myProcessSession(), mode, timeout);
  1382. conn->commit();
  1383. };
  1384. unsigned numThreads = 100;
  1385. std::vector<std::future<void>> results;
  1386. const char *xpath = "/Locks/TestEphemeralLock";
  1387. unsigned timeout = 2000;
  1388. for (unsigned t=0; t<numThreads; t++)
  1389. results.push_back(std::async(std::launch::async, createEphemeralLock, xpath, timeout, t%2));
  1390. for (auto &f: results)
  1391. f.get();
  1392. }
  1393. void createLevel(IPropertyTree *parent, unsigned nodeSiblings, unsigned leafSiblings, unsigned attributes, unsigned depth, unsigned level)
  1394. {
  1395. StringBuffer aname;
  1396. StringBuffer avalue;
  1397. if (2 == level) // 1st level down
  1398. {
  1399. printf(".");
  1400. fflush(stdout);
  1401. }
  1402. unsigned levelSiblings = depth==level ? leafSiblings : nodeSiblings;
  1403. for (unsigned s=0; s<levelSiblings; s++)
  1404. {
  1405. IPropertyTree *child = parent->addPropTree("Child");
  1406. if (level<depth)
  1407. createLevel(child, nodeSiblings, leafSiblings, attributes, depth, level+1);
  1408. for (unsigned a=0; a<attributes; a++)
  1409. {
  1410. avalue.clear().appendf("%u_%u", level, s+1);
  1411. child->setProp(aname.clear().appendf("@aname%u", a+1), avalue.str());
  1412. }
  1413. }
  1414. if (1 == level) // back at top
  1415. printf("\n");
  1416. }
  1417. static StringBuffer &constructXPath(StringBuffer &xpath, unsigned depth, unsigned nodeSibling, unsigned leafSibling, unsigned attr, unsigned level, const char *extra=nullptr)
  1418. {
  1419. while (true)
  1420. {
  1421. unsigned sibling = depth == level ? leafSibling : nodeSibling;
  1422. xpath.appendf("Child[@aname%u=\"%u_%u", attr, level, sibling);
  1423. if (extra && (depth == level))
  1424. xpath.append(extra);
  1425. xpath.append("\"]");
  1426. if (level == depth)
  1427. break;
  1428. xpath.append('/');
  1429. level++;
  1430. }
  1431. return xpath;
  1432. }
  1433. void createSiblings(IPropertyTree *root, unsigned depth, unsigned attributes, unsigned nodeSiblings, unsigned leafSiblings)
  1434. {
  1435. unsigned nodes = 0;
  1436. for (unsigned d=1; d<=depth; d++)
  1437. {
  1438. if (d==depth)
  1439. nodes += pow(nodeSiblings, d-1) * leafSiblings;
  1440. else
  1441. nodes += pow(nodeSiblings, d);
  1442. }
  1443. printf("Creating %u nodes\n", nodes);
  1444. CCycleTimer timer;
  1445. createLevel(root, nodeSiblings, leafSiblings, attributes, depth, 1);
  1446. printf("%6u ms : create time\n", timer.elapsedMs());
  1447. }
  1448. void testSiblingPerf(std::function<IPropertyTree *(StringBuffer &, unsigned, unsigned, unsigned, unsigned, const char *)> searchFunc, unsigned depth, unsigned attributes, unsigned nodeSiblings, unsigned leafSiblings, unsigned secondaryTests)
  1449. {
  1450. try
  1451. {
  1452. StringBuffer xpath;
  1453. StringBuffer v;
  1454. xpath.ensureCapacity(1024);
  1455. v.ensureCapacity(1024);
  1456. auto firstSearchFunc = [&](unsigned attr)
  1457. {
  1458. CCycleTimer timer;
  1459. Owned<IPropertyTree> search = searchFunc(xpath.clear(), attr, depth, nodeSiblings, leafSiblings, nullptr);
  1460. assertex(search);
  1461. printf("%6u ms : 1st search (xpath=%s) time\n", timer.elapsedMs(), xpath.str());
  1462. xpath.clear().appendf("@aname%u", attributes);
  1463. v.clear().appendf("%u_%u", depth, leafSiblings);
  1464. assertex(streq(v, search->queryProp(xpath)));
  1465. };
  1466. auto secondarySearchFunc = [&](unsigned attr, const char *extra=nullptr, unsigned siblingOffset=0)->unsigned
  1467. {
  1468. unsigned leafSibling = siblingOffset+leafSiblings-1;
  1469. unsigned foundCount = 0;
  1470. CCycleTimer timer;
  1471. for (unsigned t=0; t<secondaryTests; t++)
  1472. {
  1473. Owned<IPropertyTree> search = searchFunc(xpath.clear(), attr, depth, nodeSiblings, leafSibling, extra);
  1474. if (search)
  1475. foundCount++;
  1476. leafSibling--;
  1477. if (siblingOffset == leafSibling)
  1478. leafSibling = siblingOffset+leafSiblings;
  1479. }
  1480. VStringBuffer msg("%6u ms : Next ", timer.elapsedMs());
  1481. msg.append(secondaryTests).append(" searches for aname").append(attr);
  1482. if (extra)
  1483. msg.append(" [extra=\"").append(extra).append("\"]");
  1484. msg.append(" time");
  1485. printf("%s\n", msg.str());
  1486. return foundCount;
  1487. };
  1488. firstSearchFunc(1); // first attribute
  1489. verifyex(secondaryTests == secondarySearchFunc(1)); // first attribute
  1490. firstSearchFunc(attributes); // 1st attribute
  1491. verifyex(secondaryTests == secondarySearchFunc(attributes)); // last attribute
  1492. verifyex(secondaryTests == secondarySearchFunc(1)); // first attribute
  1493. Owned<IPropertyTree> parent = searchFunc(xpath.clear(), 1, depth-1, nodeSiblings, nodeSiblings, nullptr);
  1494. verifyex(parent);
  1495. CCycleTimer timer;
  1496. unsigned max = leafSiblings;
  1497. if (max > 1000)
  1498. max = 1000;
  1499. unsigned removedEntries = 0, newEntries = 0, changedEntries = 0;
  1500. unsigned step = leafSiblings / max;
  1501. unsigned s = 1;
  1502. unsigned which = 0;
  1503. while (true)
  1504. {
  1505. constructXPath(xpath.clear(), depth, nodeSiblings, s, 1, depth);
  1506. IPropertyTree *search = parent->queryPropTree(xpath);
  1507. assertex(search);
  1508. switch (which)
  1509. {
  1510. case 0:
  1511. {
  1512. verifyex(parent->removeTree(search));
  1513. removedEntries++;
  1514. break;
  1515. }
  1516. case 1:
  1517. {
  1518. IPropertyTree *newChild = parent->addPropTree("Child");
  1519. newChild->setProp("@aname1", "NEW");
  1520. newEntries++;
  1521. break;
  1522. }
  1523. case 2:
  1524. {
  1525. search->setProp("@aname1", v.clear().appendf("%u_%u - CHANGED", depth, s));
  1526. changedEntries++;
  1527. break;
  1528. }
  1529. }
  1530. s += step;
  1531. if (s >= leafSiblings)
  1532. break;
  1533. ++which;
  1534. if (which>2)
  1535. which = 0;
  1536. }
  1537. printf("%6u ms : Modify, delete and create elements time\n", timer.elapsedMs());
  1538. parent.clear(); // if Dali test, then this will commit the above changes
  1539. parent.setown(searchFunc(xpath.clear(), 1, depth-1, nodeSiblings, nodeSiblings, nullptr));
  1540. timer.reset();
  1541. Owned<IPropertyTreeIterator> iter = parent->getElements(xpath.clear().append("Child[@aname1=\"NEW\"]"));
  1542. unsigned count = 0;
  1543. ForEach (*iter)
  1544. ++count;
  1545. assertex(count == newEntries);
  1546. printf("%6u ms : Scan of new entries time\n", timer.elapsedMs());
  1547. s = 1;
  1548. which = 0;
  1549. timer.reset();
  1550. while (true)
  1551. {
  1552. if (which==2)
  1553. {
  1554. Owned<IPropertyTree> search = searchFunc(xpath.clear(), 1, depth, nodeSiblings, s, " - CHANGED");
  1555. assertex(search);
  1556. }
  1557. s += step;
  1558. if (s >= leafSiblings)
  1559. break;
  1560. which++;
  1561. if (which>2)
  1562. which = 0;
  1563. }
  1564. printf("%6u ms : scans for %u changed entries time\n", timer.elapsedMs(), changedEntries);
  1565. }
  1566. catch (IException *e)
  1567. {
  1568. EXCLOG(e, nullptr);
  1569. e->Release();
  1570. }
  1571. }
  1572. void testSiblingPerfLocal()
  1573. {
  1574. unsigned depth = 3;
  1575. unsigned attributes = 3;
  1576. unsigned nodeSiblings = 10;
  1577. unsigned leafSiblings = 100000;
  1578. unsigned secondaryTests = 1000;
  1579. unsigned mappingThreshold = 10;
  1580. printf("Performing testSiblingPerfLocal\n\n");
  1581. Owned<IPropertyTree> root = createPTree();
  1582. auto searchFunc = [&root](StringBuffer &xpath, unsigned attr, unsigned depth, unsigned nodeSiblings, unsigned leafSibling, const char *extra=nullptr)->IPropertyTree *
  1583. {
  1584. constructXPath(xpath.clear(), depth, nodeSiblings, leafSibling, attr, 1, extra);
  1585. return root->getPropTree(xpath.str());
  1586. };
  1587. createSiblings(root, depth, attributes, nodeSiblings, leafSiblings);
  1588. printf("cloning tree for 2nd test\n");
  1589. Owned<IPropertyTree> copyRoot = createPTreeFromIPT(root);
  1590. setPTreeMappingThreshold(0); // disable
  1591. printf("Performing tests with mapping disabled\n");
  1592. testSiblingPerf(searchFunc, depth, attributes, nodeSiblings, leafSiblings, secondaryTests);
  1593. root.setown(copyRoot.getClear());
  1594. setPTreeMappingThreshold(mappingThreshold);
  1595. printf("Performing tests with mapping enabled (mappingThreshold=%u)\n", mappingThreshold);
  1596. testSiblingPerf(searchFunc, depth, attributes, nodeSiblings, leafSiblings, secondaryTests);
  1597. }
  1598. void testSiblingPerfDali()
  1599. {
  1600. unsigned depth = 3;
  1601. unsigned attributes = 3;
  1602. unsigned nodeSiblings = 2;
  1603. unsigned leafSiblings = 100000;
  1604. unsigned secondaryTests = 100;
  1605. unsigned mappingThreshold = 10;
  1606. printf("Performing testSiblingPerfDali\n\n");
  1607. setPTreeMappingThreshold(mappingThreshold);
  1608. Owned<IRemoteConnection> conn = querySDS().connect("/testmaps", myProcessSession(), RTM_CREATE, 10000);
  1609. createSiblings(conn->queryRoot(), depth, attributes, nodeSiblings, leafSiblings);
  1610. conn.clear(); // commit
  1611. auto searchFunc = [](StringBuffer &xpath, unsigned attr, unsigned depth, unsigned nodeSiblings, unsigned leafSibling, const char *extra=nullptr)->IPropertyTree *
  1612. {
  1613. constructXPath(xpath.clear().append("/testmaps/"), depth, nodeSiblings, leafSibling, attr, 1, extra);
  1614. Owned<IRemoteConnection> conn = querySDS().connect(xpath.str(), myProcessSession(), 0, 10000);
  1615. assertex(conn);
  1616. return conn->getRoot();
  1617. };
  1618. printf("Performing tests with both client and server side mapping enabled (mappingThreshold=%u)\n", mappingThreshold);
  1619. testSiblingPerf(searchFunc, depth, attributes, nodeSiblings, leafSiblings, secondaryTests);
  1620. }
  1621. void testSiblingPerfContention()
  1622. {
  1623. unsigned depth = 1;
  1624. unsigned attributes = 10;
  1625. unsigned nodeSiblings = 1;
  1626. unsigned leafSiblings = 1000;
  1627. unsigned mappingThreshold = 10;
  1628. unsigned threads = 20;
  1629. printf("Performing testSiblingPerfContention\n\n");
  1630. Owned<IPropertyTree> root = createPTree();
  1631. createSiblings(root, depth, attributes, nodeSiblings, leafSiblings);
  1632. setPTreeMappingThreshold(mappingThreshold);
  1633. std::vector<std::future<void>> results;
  1634. auto searchFunc = [&root](unsigned a)
  1635. {
  1636. /*
  1637. * NB: initially the 1st lookups are likely to clash and only 1 will create the initial map.
  1638. * i.e. the other threads will not use the map.
  1639. */
  1640. StringBuffer xpath;
  1641. for (unsigned s=1; s<=1000; s++)
  1642. {
  1643. constructXPath(xpath.clear(), 1, 1, s, a, 1, nullptr);
  1644. IPropertyTree *search = root->queryPropTree(xpath);
  1645. assertex(search);
  1646. }
  1647. };
  1648. for (unsigned t=0; t<threads; t++)
  1649. {
  1650. unsigned a = (t % (threads/2))+1;
  1651. results.push_back(std::async(std::launch::async, searchFunc, a));
  1652. }
  1653. for (auto &f: results)
  1654. f.get();
  1655. }
  1656. };
  1657. CPPUNIT_TEST_SUITE_REGISTRATION( CDaliSDSStressTests );
  1658. CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( CDaliSDSStressTests, "CDaliSDSStressTests" );
  1659. // ================================================================================== UNIT TESTS
  1660. static IFileDescriptor *createDescriptor(const char* dir, const char* name, unsigned parts, unsigned recSize, unsigned index=0)
  1661. {
  1662. Owned<IPropertyTree> pp = createPTree("Part");
  1663. Owned<IFileDescriptor>fdesc = createFileDescriptor();
  1664. fdesc->setDefaultDir(dir);
  1665. StringBuffer s;
  1666. SocketEndpoint ep;
  1667. ep.setLocalHost(0);
  1668. StringBuffer ip;
  1669. ep.getIpText(ip);
  1670. for (unsigned k=0;k<parts;k++) {
  1671. s.clear().append(ip);
  1672. Owned<INode> node = createINode(s.str());
  1673. pp->setPropInt64("@size",recSize);
  1674. s.clear().append(name);
  1675. if (index)
  1676. s.append(index);
  1677. s.append("._").append(k+1).append("_of_").append(parts);
  1678. fdesc->setPart(k,node,s.str(),pp);
  1679. }
  1680. fdesc->queryProperties().setPropInt("@recordSize",recSize);
  1681. fdesc->setDefaultDir(dir);
  1682. return fdesc.getClear();
  1683. }
  1684. static void setupDFS(const IContextLogger &logctx, const char *scope, unsigned supersToDel=3, unsigned subsToCreate=4)
  1685. {
  1686. StringBuffer bufScope;
  1687. bufScope.append("regress::").append(scope);
  1688. StringBuffer bufDir;
  1689. bufDir.append("regress/").append(scope);
  1690. logctx.CTXLOG("Cleaning up '%s' scope", bufScope.str());
  1691. for (unsigned i=1; i<=supersToDel; i++) {
  1692. StringBuffer super(bufScope);
  1693. super.append("::super").append(i);
  1694. if (dir.exists(super.str(),user,false,true))
  1695. ASSERT(dir.removeEntry(super.str(), user) && "Can't remove super-file");
  1696. }
  1697. logctx.CTXLOG("Creating 'regress::trans' subfiles(1,%d)", subsToCreate);
  1698. for (unsigned i=1; i<=subsToCreate; i++) {
  1699. StringBuffer name;
  1700. name.append("sub").append(i);
  1701. StringBuffer sub(bufScope);
  1702. sub.append("::").append(name);
  1703. // Remove first
  1704. if (dir.exists(sub.str(),user,true,false))
  1705. ASSERT(dir.removeEntry(sub.str(), user) && "Can't remove sub-file");
  1706. try {
  1707. // Create the sub file with an arbitrary format
  1708. Owned<IFileDescriptor> subd = createDescriptor(bufDir.str(), name.str(), 1, 17);
  1709. Owned<IPartDescriptor> partd = subd->getPart(0);
  1710. RemoteFilename rfn;
  1711. partd->getFilename(0, rfn);
  1712. StringBuffer fname;
  1713. rfn.getPath(fname);
  1714. recursiveCreateDirectoryForFile(fname.str());
  1715. OwnedIFile ifile = createIFile(fname.str());
  1716. Owned<IFileIO> io;
  1717. io.setown(ifile->open(IFOcreate));
  1718. io->write(0, 17, "12345678901234567");
  1719. io->close();
  1720. Owned<IDistributedFile> dsub = dir.createNew(subd);
  1721. dsub->attach(sub.str(),user);
  1722. } catch (IException *e) {
  1723. StringBuffer msg;
  1724. e->errorMessage(msg);
  1725. logctx.CTXLOG("Caught exception while creating file in DFS: %s", msg.str());
  1726. e->Release();
  1727. ASSERT(0 && "Exception Caught in setupDFS - is the directory writeable by this user?");
  1728. }
  1729. // Make sure it got created
  1730. ASSERT(dir.exists(sub.str(),user,true,false) && "Can't add physical files");
  1731. }
  1732. }
  1733. class CDaliDFSStressTests : public CppUnit::TestFixture
  1734. {
  1735. CPPUNIT_TEST_SUITE(CDaliDFSStressTests);
  1736. CPPUNIT_TEST(testInit);
  1737. CPPUNIT_TEST(testGroups);
  1738. CPPUNIT_TEST(testMultiCluster);
  1739. CPPUNIT_TEST(testDFSTrans);
  1740. CPPUNIT_TEST(testDFSPromote);
  1741. CPPUNIT_TEST(testDFSDel);
  1742. CPPUNIT_TEST(testDFSRename);
  1743. CPPUNIT_TEST(testDFSClearAdd);
  1744. CPPUNIT_TEST(testDFSRename2);
  1745. CPPUNIT_TEST(testDFSRenameThenDelete);
  1746. CPPUNIT_TEST(testDFSRemoveSuperSub);
  1747. // This test requires access to an external IP with dafilesrv running
  1748. // CPPUNIT_TEST(testDFSRename3);
  1749. CPPUNIT_TEST_SUITE_END();
  1750. void testGrp(SocketEndpointArray &epa)
  1751. {
  1752. Owned<IGroup> grp = createIGroup(epa);
  1753. StringBuffer s;
  1754. grp->getText(s);
  1755. printf("'%s'\n",s.str());
  1756. Owned<IGroup> grp2 = createIGroup(s.str());
  1757. if (grp->compare(grp2)!=GRidentical)
  1758. {
  1759. s.clear().append("Group did not match: ");
  1760. grp->getText(s);
  1761. CPPUNIT_ASSERT_MESSAGE(s.str(), 0);
  1762. }
  1763. }
  1764. const IContextLogger &logctx;
  1765. public:
  1766. CDaliDFSStressTests() : logctx(queryDummyContextLogger())
  1767. {
  1768. }
  1769. ~CDaliDFSStressTests()
  1770. {
  1771. daliClientEnd();
  1772. }
  1773. void testInit()
  1774. {
  1775. daliClientInit();
  1776. }
  1777. void testDFSTrans()
  1778. {
  1779. setupDFS(logctx, "trans");
  1780. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user);
  1781. // Auto-commit
  1782. logctx.CTXLOG("Auto-commit test (inactive transaction)");
  1783. Owned<IDistributedSuperFile> sfile1 = dir.createSuperFile("regress::trans::super1",user , false, false, transaction);
  1784. sfile1->addSubFile("regress::trans::sub1", false, NULL, false, transaction);
  1785. sfile1->addSubFile("regress::trans::sub2", false, NULL, false, transaction);
  1786. sfile1.clear();
  1787. sfile1.setown(dir.lookupSuperFile("regress::trans::super1", user, transaction));
  1788. ASSERT(sfile1.get() && "non-transactional add super1 failed");
  1789. ASSERT(sfile1->numSubFiles() == 2 && "auto-commit add sub failed, not all subs were added");
  1790. ASSERT(strcmp(sfile1->querySubFile(0).queryLogicalName(), "regress::trans::sub1") == 0 && "auto-commit add sub failed, wrong name for sub1");
  1791. ASSERT(strcmp(sfile1->querySubFile(1).queryLogicalName(), "regress::trans::sub2") == 0 && "auto-commit add sub failed, wrong name for sub2");
  1792. sfile1.clear();
  1793. // Rollback
  1794. logctx.CTXLOG("Rollback test (active transaction)");
  1795. transaction->start();
  1796. Owned<IDistributedSuperFile> sfile2 = dir.createSuperFile("regress::trans::super2", user, false, false, transaction);
  1797. sfile2->addSubFile("regress::trans::sub3", false, NULL, false, transaction);
  1798. sfile2->addSubFile("regress::trans::sub4", false, NULL, false, transaction);
  1799. transaction->rollback();
  1800. ASSERT(sfile2->numSubFiles() == 0 && "transactional rollback failed, some subs were added");
  1801. sfile2.clear();
  1802. sfile2.setown(dir.lookupSuperFile("regress::trans::super2", user, transaction));
  1803. ASSERT(!sfile2.get() && "transactional rollback super2 failed, it exists!");
  1804. // Commit
  1805. logctx.CTXLOG("Commit test (active transaction)");
  1806. transaction->start();
  1807. Owned<IDistributedSuperFile> sfile3 = dir.createSuperFile("regress::trans::super3", user, false, false, transaction);
  1808. sfile3->addSubFile("regress::trans::sub3", false, NULL, false, transaction);
  1809. sfile3->addSubFile("regress::trans::sub4", false, NULL, false, transaction);
  1810. transaction->commit();
  1811. sfile3.clear();
  1812. sfile3.setown(dir.lookupSuperFile("regress::trans::super3", user, transaction));
  1813. ASSERT(sfile3.get() && "transactional add super3 failed");
  1814. ASSERT(sfile3->numSubFiles() == 2 && "transactional add sub failed, not all subs were added");
  1815. ASSERT(strcmp(sfile3->querySubFile(0).queryLogicalName(), "regress::trans::sub3") == 0 && "transactional add sub failed, wrong name for sub3");
  1816. ASSERT(strcmp(sfile3->querySubFile(1).queryLogicalName(), "regress::trans::sub4") == 0 && "transactional add sub failed, wrong name for sub4");
  1817. sfile3.clear();
  1818. }
  1819. void testDFSPromote()
  1820. {
  1821. setupDFS(logctx, "trans");
  1822. unsigned timeout = 1000; // 1s
  1823. /* Make the meta info of one of the subfiles mismatch the rest, as subfiles are promoted through
  1824. * the super files, this should _not_ cause an issue, as no single super file will contain
  1825. * mismatched subfiles.
  1826. */
  1827. Owned<IDistributedFile> sub1 = dir.lookup("regress::trans::sub1", user, AccessMode::tbdRead, false, false, NULL, false, timeout);
  1828. assertex(sub1);
  1829. sub1->lockProperties();
  1830. sub1->queryAttributes().setPropBool("@local", true);
  1831. sub1->unlockProperties();
  1832. sub1.clear();
  1833. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user);
  1834. // ===============================================================================
  1835. // Don't change these parameters, or you'll have to change all ERROR tests below
  1836. const char *sfnames[3] = {
  1837. "regress::trans::super1", "regress::trans::super2", "regress::trans::super3"
  1838. };
  1839. bool delsub = false;
  1840. bool createonlyone = true;
  1841. // ===============================================================================
  1842. StringArray outlinked;
  1843. logctx.CTXLOG("Promote (1, -, -) - first iteration");
  1844. dir.promoteSuperFiles(3, sfnames, "regress::trans::sub1", delsub, createonlyone, user, timeout, outlinked);
  1845. {
  1846. Owned<IDistributedSuperFile> sfile1 = dir.lookupSuperFile("regress::trans::super1", user, NULL, timeout);
  1847. ASSERT(sfile1.get() && "promote failed, super1 doesn't exist");
  1848. ASSERT(sfile1->numSubFiles() == 1 && "promote failed, super1 should have one subfile");
  1849. ASSERT(strcmp(sfile1->querySubFile(0).queryLogicalName(), "regress::trans::sub1") == 0 && "promote failed, wrong name for sub1");
  1850. Owned<IDistributedSuperFile> sfile2 = dir.lookupSuperFile("regress::trans::super2", user, NULL, timeout);
  1851. ASSERT(!sfile2.get() && "promote failed, super2 does exist");
  1852. ASSERT(outlinked.length() == 0 && "promote failed, outlinked expected empty");
  1853. }
  1854. logctx.CTXLOG("Promote (2, 1, -) - second iteration");
  1855. dir.promoteSuperFiles(3, sfnames, "regress::trans::sub2", delsub, createonlyone, user, timeout, outlinked);
  1856. {
  1857. Owned<IDistributedSuperFile> sfile1 = dir.lookupSuperFile("regress::trans::super1", user, NULL, timeout);
  1858. ASSERT(sfile1.get() && "promote failed, super1 doesn't exist");
  1859. ASSERT(sfile1->numSubFiles() == 1 && "promote failed, super1 should have one subfile");
  1860. ASSERT(strcmp(sfile1->querySubFile(0).queryLogicalName(), "regress::trans::sub2") == 0 && "promote failed, wrong name for sub2");
  1861. Owned<IDistributedSuperFile> sfile2 = dir.lookupSuperFile("regress::trans::super2", user, NULL, timeout);
  1862. ASSERT(sfile2.get() && "promote failed, super2 doesn't exist");
  1863. ASSERT(sfile2->numSubFiles() == 1 && "promote failed, super2 should have one subfile");
  1864. ASSERT(strcmp(sfile2->querySubFile(0).queryLogicalName(), "regress::trans::sub1") == 0 && "promote failed, wrong name for sub1");
  1865. Owned<IDistributedSuperFile> sfile3 = dir.lookupSuperFile("regress::trans::super3", user, NULL, timeout);
  1866. ASSERT(!sfile3.get() && "promote failed, super3 does exist");
  1867. ASSERT(outlinked.length() == 0 && "promote failed, outlinked expected empty");
  1868. }
  1869. logctx.CTXLOG("Promote (3, 2, 1) - third iteration");
  1870. dir.promoteSuperFiles(3, sfnames, "regress::trans::sub3", delsub, createonlyone, user, timeout, outlinked);
  1871. {
  1872. Owned<IDistributedSuperFile> sfile1 = dir.lookupSuperFile("regress::trans::super1", user, NULL, timeout);
  1873. ASSERT(sfile1.get() &&* "promote failed, super1 doesn't exist");
  1874. ASSERT(sfile1->numSubFiles() == 1 && "promote failed, super1 should have one subfile");
  1875. ASSERT(strcmp(sfile1->querySubFile(0).queryLogicalName(), "regress::trans::sub3") == 0 && "promote failed, wrong name for sub3");
  1876. Owned<IDistributedSuperFile> sfile2 = dir.lookupSuperFile("regress::trans::super2", user, NULL, timeout);
  1877. ASSERT(sfile2.get() && "promote failed, super2 doesn't exist");
  1878. ASSERT(sfile2->numSubFiles() == 1 && "promote failed, super2 should have one subfile");
  1879. ASSERT(strcmp(sfile2->querySubFile(0).queryLogicalName(), "regress::trans::sub2") == 0 && "promote failed, wrong name for sub2");
  1880. Owned<IDistributedSuperFile> sfile3 = dir.lookupSuperFile("regress::trans::super3", user, NULL, timeout);
  1881. ASSERT(sfile3.get() && "promote failed, super3 doesn't exist");
  1882. ASSERT(sfile3->numSubFiles() == 1 && "promote failed, super3 should have one subfile");
  1883. ASSERT(strcmp(sfile3->querySubFile(0).queryLogicalName(), "regress::trans::sub1") == 0 && "promote failed, wrong name for sub1");
  1884. ASSERT(outlinked.length() == 0 && "promote failed, outlinked expected empty");
  1885. }
  1886. logctx.CTXLOG("Promote (4, 3, 2) - fourth iteration, expect outlinked");
  1887. dir.promoteSuperFiles(3, sfnames, "regress::trans::sub4", delsub, createonlyone, user, timeout, outlinked);
  1888. {
  1889. Owned<IDistributedSuperFile> sfile1 = dir.lookupSuperFile("regress::trans::super1", user, NULL, timeout);
  1890. ASSERT(sfile1.get() && "promote failed, super1 doesn't exist");
  1891. ASSERT(sfile1->numSubFiles() == 1 && "promote failed, super1 should have one subfile");
  1892. ASSERT(strcmp(sfile1->querySubFile(0).queryLogicalName(), "regress::trans::sub4") == 0 && "promote failed, wrong name for sub4");
  1893. Owned<IDistributedSuperFile> sfile2 = dir.lookupSuperFile("regress::trans::super2", user, NULL, timeout);
  1894. ASSERT(sfile2.get() && "promote failed, super2 doesn't exist");
  1895. ASSERT(sfile2->numSubFiles() == 1 && "promote failed, super2 should have one subfile");
  1896. ASSERT(strcmp(sfile2->querySubFile(0).queryLogicalName(), "regress::trans::sub3") == 0 && "promote failed, wrong name for sub3");
  1897. Owned<IDistributedSuperFile> sfile3 = dir.lookupSuperFile("regress::trans::super3", user, NULL, timeout);
  1898. ASSERT(sfile3.get() && "promote failed, super3 doesn't exist");
  1899. ASSERT(sfile3->numSubFiles() == 1 && "promote failed, super3 should have one subfile");
  1900. ASSERT(strcmp(sfile3->querySubFile(0).queryLogicalName(), "regress::trans::sub2") == 0 && "promote failed, wrong name for sub2");
  1901. ASSERT(outlinked.length() == 1 && "promote failed, outlinked expected only one item");
  1902. ASSERT(strcmp(outlinked.popGet(), "regress::trans::sub1") == 0 && "promote failed, outlinked expected to be sub1");
  1903. Owned<IDistributedFile> sub1 = dir.lookup("regress::trans::sub1", user, AccessMode::tbdRead, false, false, NULL, false, timeout);
  1904. ASSERT(sub1.get() && "promote failed, sub1 was physically deleted");
  1905. }
  1906. logctx.CTXLOG("Promote ([2,3], 4, 3) - fifth iteration, two in-files");
  1907. dir.promoteSuperFiles(3, sfnames, "regress::trans::sub2,regress::trans::sub3", delsub, createonlyone, user, timeout, outlinked);
  1908. {
  1909. Owned<IDistributedSuperFile> sfile1 = dir.lookupSuperFile("regress::trans::super1", user, NULL, timeout);
  1910. ASSERT(sfile1.get() && "promote failed, super1 doesn't exist");
  1911. ASSERT(sfile1->numSubFiles() == 2 && "promote failed, super1 should have two subfiles");
  1912. ASSERT(strcmp(sfile1->querySubFile(0).queryLogicalName(), "regress::trans::sub2") == 0 && "promote failed, wrong name for sub1");
  1913. ASSERT(strcmp(sfile1->querySubFile(1).queryLogicalName(), "regress::trans::sub3") == 0 && "promote failed, wrong name for sub2");
  1914. Owned<IDistributedSuperFile> sfile2 = dir.lookupSuperFile("regress::trans::super2", user, NULL, timeout);
  1915. ASSERT(sfile2.get() && "promote failed, super2 doesn't exist");
  1916. ASSERT(sfile2->numSubFiles() == 1 && "promote failed, super2 should have one subfile");
  1917. ASSERT(strcmp(sfile2->querySubFile(0).queryLogicalName(), "regress::trans::sub4") == 0 && "promote failed, wrong name for sub4");
  1918. Owned<IDistributedSuperFile> sfile3 = dir.lookupSuperFile("regress::trans::super3", user, NULL, timeout);
  1919. ASSERT(sfile3.get() && "promote failed, super3 doesn't exist");
  1920. ASSERT(sfile3->numSubFiles() == 1 && "promote failed, super3 should have one subfile");
  1921. ASSERT(strcmp(sfile3->querySubFile(0).queryLogicalName(), "regress::trans::sub3") == 0 && "promote failed, wrong name for sub3");
  1922. ASSERT(outlinked.length() == 1 && "promote failed, outlinked expected only one item");
  1923. ASSERT(strcmp(outlinked.popGet(), "regress::trans::sub2") == 0 && "promote failed, outlinked expected to be sub2");
  1924. Owned<IDistributedFile> sub1 = dir.lookup("regress::trans::sub1", user, AccessMode::tbdRead, false, false, NULL, false, timeout);
  1925. ASSERT(sub1.get() && "promote failed, sub1 was physically deleted");
  1926. Owned<IDistributedFile> sub2 = dir.lookup("regress::trans::sub2", user, AccessMode::tbdRead, false, false, NULL, false, timeout);
  1927. ASSERT(sub2.get() && "promote failed, sub2 was physically deleted");
  1928. }
  1929. }
  1930. void testMultiCluster()
  1931. {
  1932. queryNamedGroupStore().add("testgrp1", { "192.168.51.1-5" });
  1933. queryNamedGroupStore().add("testgrp2", { "192.168.16.1-5" });
  1934. queryNamedGroupStore().add("testgrp3", { "192.168.53.1-5" });
  1935. Owned<IFileDescriptor> fdesc = createFileDescriptor();
  1936. fdesc->setDefaultDir("/c$/thordata/test");
  1937. fdesc->setPartMask("testfile1._$P$_of_$N$");
  1938. fdesc->setNumParts(5);
  1939. for (unsigned p=0; p<fdesc->numParts(); p++)
  1940. fdesc->queryPart(p)->queryProperties().setPropInt64("@size", 10);
  1941. ClusterPartDiskMapSpec mapping;
  1942. fdesc->addCluster("testgrp1", nullptr, mapping);
  1943. fdesc->addCluster("testgrp2", nullptr, mapping);
  1944. fdesc->addCluster("testgrp3", nullptr, mapping);
  1945. removeLogical("test::testfile1", user);
  1946. Owned<IDistributedFile> file = queryDistributedFileDirectory().createNew(fdesc);
  1947. removeLogical("test::testfile1", user);
  1948. file->attach("test::testfile1",user);
  1949. StringBuffer name;
  1950. unsigned i;
  1951. for (i=0;i<file->numClusters();i++)
  1952. PROGLOG("cluster[%d] = %s",i,file->getClusterName(i,name.clear()).str());
  1953. file.clear();
  1954. file.setown(queryDistributedFileDirectory().lookup("test::testfile1",user,AccessMode::tbdRead,false,false,nullptr,defaultNonPrivilegedUser));
  1955. for (i=0;i<file->numClusters();i++)
  1956. PROGLOG("cluster[%d] = %s",i,file->getClusterName(i,name.clear()).str());
  1957. file.clear();
  1958. file.setown(queryDistributedFileDirectory().lookup("test::testfile1@testgrp1",user,AccessMode::tbdRead,false,false,nullptr,defaultNonPrivilegedUser));
  1959. for (i=0;i<file->numClusters();i++)
  1960. PROGLOG("cluster[%d] = %s",i,file->getClusterName(i,name.clear()).str());
  1961. file.clear();
  1962. removeLogical("test::testfile1@testgrp2", user);
  1963. file.setown(queryDistributedFileDirectory().lookup("test::testfile1",user,AccessMode::tbdRead,false,false,nullptr,defaultNonPrivilegedUser));
  1964. for (i=0;i<file->numClusters();i++)
  1965. PROGLOG("cluster[%d] = %s",i,file->getClusterName(i,name.clear()).str());
  1966. }
  1967. void testGroups()
  1968. {
  1969. SocketEndpointArray epa;
  1970. SocketEndpoint ep;
  1971. Owned<IGroup> grp;
  1972. ep.set("10.150.10.80");
  1973. epa.append(ep);
  1974. testGrp(epa);
  1975. ep.set("10.150.10.81");
  1976. epa.append(ep);
  1977. testGrp(epa);
  1978. ep.set("10.150.10.82");
  1979. epa.append(ep);
  1980. testGrp(epa);
  1981. ep.set("10.150.10.83:111");
  1982. epa.append(ep);
  1983. testGrp(epa);
  1984. ep.set("10.150.10.84:111");
  1985. epa.append(ep);
  1986. testGrp(epa);
  1987. ep.set("10.150.10.84:111");
  1988. epa.append(ep);
  1989. testGrp(epa);
  1990. ep.set("10.150.10.84:111");
  1991. epa.append(ep);
  1992. testGrp(epa);
  1993. ep.set("10.150.10.84:111");
  1994. epa.append(ep);
  1995. testGrp(epa);
  1996. ep.set("10.150.10.85:111");
  1997. epa.append(ep);
  1998. testGrp(epa);
  1999. ep.set("10.150.10.86:111");
  2000. epa.append(ep);
  2001. testGrp(epa);
  2002. ep.set("10.150.10.87");
  2003. epa.append(ep);
  2004. testGrp(epa);
  2005. ep.set("10.150.10.87");
  2006. epa.append(ep);
  2007. testGrp(epa);
  2008. ep.set("10.150.10.88");
  2009. epa.append(ep);
  2010. testGrp(epa);
  2011. ep.set("10.150.11.88");
  2012. epa.append(ep);
  2013. testGrp(epa);
  2014. ep.set("10.173.10.88");
  2015. epa.append(ep);
  2016. testGrp(epa);
  2017. ep.set("10.173.10.88:22222");
  2018. epa.append(ep);
  2019. testGrp(epa);
  2020. ep.set("192.168.10.88");
  2021. epa.append(ep);
  2022. testGrp(epa);
  2023. }
  2024. void testDFSDel()
  2025. {
  2026. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user); // disabled, auto-commit
  2027. setupDFS(logctx, "del");
  2028. // Sub-file deletion
  2029. logctx.CTXLOG("Creating regress::del::super1 and attaching sub");
  2030. Owned<IDistributedSuperFile> sfile = dir.createSuperFile("regress::del::super1", user, false, false, transaction);
  2031. sfile->addSubFile("regress::del::sub1", false, NULL, false, transaction);
  2032. sfile->addSubFile("regress::del::sub4", false, NULL, false, transaction);
  2033. sfile.clear();
  2034. logctx.CTXLOG("Deleting regress::del::sub1, should fail");
  2035. try {
  2036. if (dir.removeEntry("regress::del::sub1", user, transaction)) {
  2037. ASSERT(0 && "Could remove sub, this will make the DFS inconsistent!");
  2038. return;
  2039. }
  2040. } catch (IException *e) {
  2041. // expecting an exception
  2042. e->Release();
  2043. }
  2044. logctx.CTXLOG("Removing regress::del::sub1 from super1, no del");
  2045. sfile.setown(transaction->lookupSuperFile("regress::del::super1"));
  2046. sfile->removeSubFile("regress::del::sub1", false, false, transaction);
  2047. ASSERT(sfile->numSubFiles() == 1 && "File sub1 was not removed from super1");
  2048. sfile.clear();
  2049. ASSERT(dir.exists("regress::del::sub1", user) && "File sub1 was removed from the file system");
  2050. logctx.CTXLOG("Removing regress::del::sub4 from super1, del");
  2051. sfile.setown(transaction->lookupSuperFile("regress::del::super1"));
  2052. sfile->removeSubFile("regress::del::sub4", true, false, transaction);
  2053. ASSERT(!sfile->numSubFiles() && "File sub4 was not removed from super1");
  2054. sfile.clear();
  2055. ASSERT(!dir.exists("regress::del::sub4", user) && "File sub4 was NOT removed from the file system");
  2056. // Logical Remove
  2057. logctx.CTXLOG("Deleting 'regress::del::super1, should work");
  2058. ASSERT(dir.removeEntry("regress::del::super1", user) && "Can't remove super1");
  2059. logctx.CTXLOG("Deleting 'regress::del::sub1 autoCommit, should work");
  2060. ASSERT(dir.removeEntry("regress::del::sub1", user) && "Can't remove sub1");
  2061. logctx.CTXLOG("Removing 'regress::del::sub2 - rollback");
  2062. transaction->start();
  2063. dir.removeEntry("regress::del::sub2", user, transaction);
  2064. transaction->rollback();
  2065. ASSERT(dir.exists("regress::del::sub2", user, true, false) && "Shouldn't have removed sub2 on rollback");
  2066. logctx.CTXLOG("Removing 'regress::del::sub2 - commit");
  2067. transaction->start();
  2068. dir.removeEntry("regress::del::sub2", user, transaction);
  2069. transaction->commit();
  2070. ASSERT(!dir.exists("regress::del::sub2", user, true, false) && "Should have removed sub2 on commit");
  2071. // Physical Remove
  2072. logctx.CTXLOG("Physically removing 'regress::del::sub3 - rollback");
  2073. transaction->start();
  2074. dir.removeEntry("regress::del::sub3", user, transaction);
  2075. transaction->rollback();
  2076. ASSERT(dir.exists("regress::del::sub3", user, true, false) && "Shouldn't have removed sub3 on rollback");
  2077. logctx.CTXLOG("Physically removing 'regress::del::sub3 - commit");
  2078. transaction->start();
  2079. dir.removeEntry("regress::del::sub3", user, transaction);
  2080. transaction->commit();
  2081. ASSERT(!dir.exists("regress::del::sub3", user, true, false) && "Should have removed sub3 on commit");
  2082. }
  2083. void testDFSRename()
  2084. {
  2085. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user); // disabled, auto-commit
  2086. if (dir.exists("regress::rename::other1",user,false,false))
  2087. ASSERT(dir.removeEntry("regress::rename::other1", user) && "Can't remove 'regress::rename::other1'");
  2088. if (dir.exists("regress::rename::other2",user,false,false))
  2089. ASSERT(dir.removeEntry("regress::rename::other2", user) && "Can't remove 'regress::rename::other2'");
  2090. setupDFS(logctx, "rename");
  2091. try {
  2092. logctx.CTXLOG("Renaming 'regress::rename::sub1 to 'sub2' with auto-commit, should fail");
  2093. dir.renamePhysical("regress::rename::sub1", "regress::rename::sub2", user, transaction);
  2094. ASSERT(0 && "Renamed to existing file should have failed!");
  2095. return;
  2096. } catch (IException *e) {
  2097. // Expecting exception
  2098. e->Release();
  2099. }
  2100. logctx.CTXLOG("Renaming 'regress::rename::sub1 to 'other1' with auto-commit");
  2101. dir.renamePhysical("regress::rename::sub1", "regress::rename::other1", user, transaction);
  2102. ASSERT(dir.exists("regress::rename::other1", user, true, false) && "Renamed to other failed");
  2103. logctx.CTXLOG("Renaming 'regress::rename::sub2 to 'other2' and rollback");
  2104. transaction->start();
  2105. dir.renamePhysical("regress::rename::sub2", "regress::rename::other2", user, transaction);
  2106. transaction->rollback();
  2107. ASSERT(!dir.exists("regress::rename::other2", user, true, false) && "Renamed to other2 when it shouldn't");
  2108. logctx.CTXLOG("Renaming 'regress::rename::sub2 to 'other2' and commit");
  2109. transaction->start();
  2110. dir.renamePhysical("regress::rename::sub2", "regress::rename::other2", user, transaction);
  2111. transaction->commit();
  2112. ASSERT(dir.exists("regress::rename::other2", user, true, false) && "Renamed to other failed");
  2113. try {
  2114. logctx.CTXLOG("Renaming 'regress::rename::sub3 to 'sub3' with auto-commit, should fail");
  2115. dir.renamePhysical("regress::rename::sub3", "regress::rename::sub3", user, transaction);
  2116. ASSERT(0 && "Renamed to same file should have failed!");
  2117. return;
  2118. } catch (IException *e) {
  2119. // Expecting exception
  2120. e->Release();
  2121. }
  2122. // To make sure renamed files are cleaned properly
  2123. printf("Renaming 'regress::rename::other2 to 'sub2' on auto-commit\n");
  2124. dir.renamePhysical("regress::rename::other2", "regress::rename::sub2", user, transaction);
  2125. ASSERT(dir.exists("regress::rename::sub2", user, true, false) && "Renamed from other2 failed");
  2126. }
  2127. void testDFSClearAdd()
  2128. {
  2129. setupDFS(logctx, "clearadd");
  2130. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user); // disabled, auto-commit
  2131. logctx.CTXLOG("Creating regress::clearadd::super1 and attaching sub1 & sub4");
  2132. Owned<IDistributedSuperFile> sfile = dir.createSuperFile("regress::clearadd::super1", user, false, false, transaction);
  2133. sfile->addSubFile("regress::clearadd::sub1", false, NULL, false, transaction);
  2134. sfile->addSubFile("regress::clearadd::sub4", false, NULL, false, transaction);
  2135. sfile.clear();
  2136. transaction.setown(createDistributedFileTransaction(user)); // disabled, auto-commit
  2137. transaction->start();
  2138. logctx.CTXLOG("Removing sub1 from super1, within transaction");
  2139. sfile.setown(transaction->lookupSuperFile("regress::clearadd::super1"));
  2140. sfile->removeSubFile("regress::clearadd::sub1", false, false, transaction);
  2141. sfile.clear();
  2142. logctx.CTXLOG("Adding sub1 back into to super1, within transaction");
  2143. sfile.setown(transaction->lookupSuperFile("regress::clearadd::super1"));
  2144. sfile->addSubFile("regress::clearadd::sub1", false, NULL, false, transaction);
  2145. sfile.clear();
  2146. try
  2147. {
  2148. transaction->commit();
  2149. }
  2150. catch (IException *e)
  2151. {
  2152. StringBuffer eStr;
  2153. e->errorMessage(eStr);
  2154. CPPUNIT_ASSERT_MESSAGE(eStr.str(), 0);
  2155. e->Release();
  2156. }
  2157. sfile.setown(dir.lookupSuperFile("regress::clearadd::super1", user));
  2158. ASSERT(NULL != sfile->querySubFileNamed("regress::clearadd::sub1") && "regress::clearadd::sub1, should be a subfile of super1");
  2159. // same but remove all (clear)
  2160. transaction.setown(createDistributedFileTransaction(user)); // disabled, auto-commit
  2161. transaction->start();
  2162. logctx.CTXLOG("Adding sub2 into to super1, within transaction");
  2163. sfile.setown(transaction->lookupSuperFile("regress::clearadd::super1"));
  2164. sfile->addSubFile("regress::clearadd::sub2", false, NULL, false, transaction);
  2165. sfile.clear();
  2166. logctx.CTXLOG("Removing all sub files from super1, within transaction");
  2167. sfile.setown(transaction->lookupSuperFile("regress::clearadd::super1"));
  2168. sfile->removeSubFile(NULL, false, false, transaction);
  2169. sfile.clear();
  2170. logctx.CTXLOG("Adding sub2 back into to super1, within transaction");
  2171. sfile.setown(transaction->lookupSuperFile("regress::clearadd::super1"));
  2172. sfile->addSubFile("regress::clearadd::sub2", false, NULL, false, transaction);
  2173. sfile.clear();
  2174. try
  2175. {
  2176. transaction->commit();
  2177. }
  2178. catch (IException *e)
  2179. {
  2180. StringBuffer eStr;
  2181. e->errorMessage(eStr);
  2182. CPPUNIT_ASSERT_MESSAGE(eStr.str(), 0);
  2183. e->Release();
  2184. }
  2185. sfile.setown(dir.lookupSuperFile("regress::clearadd::super1", user));
  2186. ASSERT(NULL != sfile->querySubFileNamed("regress::clearadd::sub2") && "regress::clearadd::sub2, should be a subfile of super1");
  2187. ASSERT(NULL == sfile->querySubFileNamed("regress::clearadd::sub1") && "regress::clearadd::sub1, should NOT be a subfile of super1");
  2188. ASSERT(NULL == sfile->querySubFileNamed("regress::clearadd::sub4") && "regress::clearadd::sub4, should NOT be a subfile of super1");
  2189. ASSERT(1 == sfile->numSubFiles() && "regress::clearadd::super1 should contain 1 subfile");
  2190. }
  2191. void testDFSRename2()
  2192. {
  2193. setupDFS(logctx, "rename2");
  2194. /* Create a super and sub1 and sub4 in a auto-commit transaction
  2195. * Inside a transaction, do:
  2196. * a) rename sub2 to renamedsub2
  2197. * b) remove sub1
  2198. * c) add sub1
  2199. * d) add renamedsub2
  2200. * e) commit transaction
  2201. * f) renamed files existing and superfile contents
  2202. */
  2203. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user); // disabled, auto-commit
  2204. logctx.CTXLOG("Creating regress::rename2::super1 and attaching sub1 & sub4");
  2205. Owned<IDistributedSuperFile> sfile = dir.createSuperFile("regress::rename2::super1", user, false, false, transaction);
  2206. sfile->addSubFile("regress::rename2::sub1", false, NULL, false, transaction);
  2207. sfile->addSubFile("regress::rename2::sub4", false, NULL, false, transaction);
  2208. sfile.clear();
  2209. if (dir.exists("regress::rename2::renamedsub2",user,false,false))
  2210. ASSERT(dir.removeEntry("regress::rename2::renamedsub2", user) && "Can't remove 'regress::rename2::renamedsub2'");
  2211. transaction.setown(createDistributedFileTransaction(user)); // disabled, auto-commit
  2212. logctx.CTXLOG("Starting transaction");
  2213. transaction->start();
  2214. logctx.CTXLOG("Renaming regress::rename2::sub2 TO regress::rename2::renamedsub2");
  2215. dir.renamePhysical("regress::rename2::sub2", "regress::rename2::renamedsub2", user, transaction);
  2216. logctx.CTXLOG("Removing regress::rename2::sub1 from regress::rename2::super1");
  2217. sfile.setown(transaction->lookupSuperFile("regress::rename2::super1"));
  2218. sfile->removeSubFile("regress::rename2::sub1", false, false, transaction);
  2219. sfile.clear();
  2220. logctx.CTXLOG("Adding renamedsub2 to super1");
  2221. sfile.setown(transaction->lookupSuperFile("regress::rename2::super1"));
  2222. sfile->addSubFile("regress::rename2::renamedsub2", false, NULL, false, transaction);
  2223. sfile.clear();
  2224. logctx.CTXLOG("Adding back sub1 to super1");
  2225. sfile.setown(transaction->lookupSuperFile("regress::rename2::super1"));
  2226. sfile->addSubFile("regress::rename2::sub1", false, NULL, false, transaction);
  2227. sfile.clear();
  2228. try
  2229. {
  2230. logctx.CTXLOG("Committing transaction");
  2231. transaction->commit();
  2232. }
  2233. catch (IException *e)
  2234. {
  2235. StringBuffer eStr;
  2236. e->errorMessage(eStr);
  2237. CPPUNIT_ASSERT_MESSAGE(eStr.str(), 0);
  2238. e->Release();
  2239. }
  2240. transaction.clear();
  2241. // validate..
  2242. ASSERT(dir.exists("regress::rename2::renamedsub2", user, true, false) && "regress::rename2::renamedsub2 should exist now transaction committed");
  2243. sfile.setown(dir.lookupSuperFile("regress::rename2::super1", user));
  2244. ASSERT(NULL != sfile->querySubFileNamed("regress::rename2::renamedsub2") && "regress::rename2::renamedsub2, should be a subfile of super1");
  2245. ASSERT(NULL != sfile->querySubFileNamed("regress::rename2::sub1") && "regress::rename2::sub1, should be a subfile of super1");
  2246. ASSERT(NULL == sfile->querySubFileNamed("regress::rename2::sub2") && "regress::rename2::sub2, should NOT be a subfile of super1");
  2247. ASSERT(NULL != sfile->querySubFileNamed("regress::rename2::sub4") && "regress::rename2::sub4, should be a subfile of super1");
  2248. ASSERT(3 == sfile->numSubFiles() && "regress::rename2::super1 should contain 4 subfiles");
  2249. }
  2250. void testDFSRenameThenDelete()
  2251. {
  2252. setupDFS(logctx, "renamedelete");
  2253. if (dir.exists("regress::renamedelete::renamedsub2",user,false,false))
  2254. ASSERT(dir.removeEntry("regress::renamedelete::renamedsub2", user) && "Can't remove 'regress::renamedelete::renamedsub2'");
  2255. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user); // disabled, auto-commit
  2256. logctx.CTXLOG("Starting transaction");
  2257. transaction->start();
  2258. logctx.CTXLOG("Renaming regress::renamedelete::sub2 TO regress::renamedelete::renamedsub2");
  2259. dir.renamePhysical("regress::renamedelete::sub2", "regress::renamedelete::renamedsub2", user, transaction);
  2260. logctx.CTXLOG("Removing regress::renamedelete::renamedsub2");
  2261. ASSERT(dir.removeEntry("regress::renamedelete::renamedsub2", user, transaction) && "Can't remove 'regress::rename2::renamedsub2'");
  2262. try
  2263. {
  2264. logctx.CTXLOG("Committing transaction");
  2265. transaction->commit();
  2266. }
  2267. catch (IException *e)
  2268. {
  2269. StringBuffer eStr;
  2270. e->errorMessage(eStr);
  2271. CPPUNIT_ASSERT_MESSAGE(eStr.str(), 0);
  2272. e->Release();
  2273. }
  2274. transaction.clear();
  2275. // validate..
  2276. ASSERT(!dir.exists("regress::renamedelete::sub2", user, true, false) && "regress::renamedelete::sub2 should NOT exist now transaction has been committed");
  2277. ASSERT(!dir.exists("regress::renamedelete::renamedsub2", user, true, false) && "regress::renamedelete::renamedsub2 should NOT exist now transaction has been committed");
  2278. }
  2279. // NB: This test requires access (via dafilesrv) to an external IP (10.239.222.21 used below, but could be any)
  2280. void testDFSRename3()
  2281. {
  2282. setupDFS(logctx, "rename3");
  2283. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user); // disabled, auto-commit
  2284. if (dir.exists("regress::tenwayfile",user))
  2285. ASSERT(dir.removeEntry("regress::tenwayfile", user) && "Can't remove");
  2286. Owned<IFileDescriptor> fdesc = createDescriptor("regress", "tenwayfile", 1, 17);
  2287. Owned<IGroup> grp1 = createIGroup("10.239.222.1");
  2288. ClusterPartDiskMapSpec mapping;
  2289. fdesc->setClusterGroup(0, grp1);
  2290. Linked<IPartDescriptor> part = fdesc->getPart(0);
  2291. RemoteFilename rfn;
  2292. part->getFilename(0, rfn);
  2293. StringBuffer path;
  2294. rfn.getPath(path);
  2295. recursiveCreateDirectoryForFile(path.str());
  2296. OwnedIFile ifile = createIFile(path.str());
  2297. Owned<IFileIO> io;
  2298. io.setown(ifile->open(IFOcreate));
  2299. io->write(0, 17, "12345678901234567");
  2300. io->close();
  2301. Owned<IDistributedFile> dsub = dir.createNew(fdesc);
  2302. dsub->attach("regress::tenwayfile", user);
  2303. dsub.clear();
  2304. fdesc.clear();
  2305. transaction.setown(createDistributedFileTransaction(user)); // disabled, auto-commit
  2306. transaction->start();
  2307. logctx.CTXLOG("Renaming regress::rename3::sub2 TO regress::tenwayfile@mythor");
  2308. dir.renamePhysical("regress::rename3::sub2", "regress::tenwayfile@mythor", user, transaction);
  2309. try
  2310. {
  2311. transaction->commit();
  2312. }
  2313. catch (IException *e)
  2314. {
  2315. StringBuffer eStr;
  2316. e->errorMessage(eStr);
  2317. CPPUNIT_ASSERT_MESSAGE(eStr.str(), 0);
  2318. e->Release();
  2319. }
  2320. transaction.setown(createDistributedFileTransaction(user));
  2321. transaction.setown(createDistributedFileTransaction(user)); // disabled, auto-commit
  2322. transaction->start();
  2323. logctx.CTXLOG("Renaming regress::tenwayfile TO regress::rename3::sub2");
  2324. dir.renamePhysical("regress::tenwayfile@mythor", "regress::rename3::sub2", user, transaction);
  2325. try
  2326. {
  2327. transaction->commit();
  2328. }
  2329. catch (IException *e)
  2330. {
  2331. StringBuffer eStr;
  2332. e->errorMessage(eStr);
  2333. CPPUNIT_ASSERT_MESSAGE(eStr.str(), 0);
  2334. e->Release();
  2335. }
  2336. }
  2337. void testDFSRemoveSuperSub()
  2338. {
  2339. setupDFS(logctx, "removesupersub");
  2340. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user);
  2341. logctx.CTXLOG("Creating regress::removesupersub::super1 and attaching sub1 & sub4");
  2342. Owned<IDistributedSuperFile> sfile = dir.createSuperFile("regress::removesupersub::super1", user, false, false, transaction);
  2343. sfile->addSubFile("regress::removesupersub::sub1", false, NULL, false, transaction);
  2344. sfile->addSubFile("regress::removesupersub::sub4", false, NULL, false, transaction);
  2345. sfile.clear();
  2346. transaction.setown(createDistributedFileTransaction(user)); // disabled, auto-commit
  2347. logctx.CTXLOG("Starting transaction");
  2348. transaction->start();
  2349. logctx.CTXLOG("Removing super removesupersub::super1 along with it's subfiles sub1 and sub4");
  2350. dir.removeSuperFile("regress::removesupersub::super1", true, user, transaction);
  2351. try
  2352. {
  2353. logctx.CTXLOG("Committing transaction");
  2354. transaction->commit();
  2355. }
  2356. catch (IException *e)
  2357. {
  2358. StringBuffer eStr;
  2359. e->errorMessage(eStr);
  2360. CPPUNIT_ASSERT_MESSAGE(eStr.str(), 0);
  2361. e->Release();
  2362. }
  2363. transaction.clear();
  2364. // validate..
  2365. ASSERT(!dir.exists("regress::removesupersub::super1", user, true, false) && "regress::removesupersub::super1 should NOT exist");
  2366. ASSERT(!dir.exists("regress::removesupersub::sub1", user, true, false) && "regress::removesupersub::sub1 should NOT exist");
  2367. ASSERT(!dir.exists("regress::removesupersub::sub4", user, true, false) && "regress::removesupersub::sub4 should NOT exist");
  2368. }
  2369. };
  2370. CPPUNIT_TEST_SUITE_REGISTRATION( CDaliDFSStressTests );
  2371. CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( CDaliDFSStressTests, "CDaliDFSStressTests" );
  2372. class CDaliDFSRetrySlowTests : public CppUnit::TestFixture
  2373. {
  2374. CPPUNIT_TEST_SUITE(CDaliDFSRetrySlowTests);
  2375. CPPUNIT_TEST(testInit);
  2376. CPPUNIT_TEST(testDFSAddFailReAdd);
  2377. CPPUNIT_TEST(testDFSRetrySuperLock);
  2378. CPPUNIT_TEST_SUITE_END();
  2379. const IContextLogger &logctx;
  2380. public:
  2381. CDaliDFSRetrySlowTests() : logctx(queryDummyContextLogger())
  2382. {
  2383. }
  2384. ~CDaliDFSRetrySlowTests()
  2385. {
  2386. daliClientEnd();
  2387. }
  2388. void testInit()
  2389. {
  2390. daliClientInit();
  2391. }
  2392. class CShortLock : implements IThreaded
  2393. {
  2394. StringAttr fileName;
  2395. unsigned secDelay;
  2396. CThreaded threaded;
  2397. public:
  2398. CShortLock(const char *_fileName, unsigned _secDelay) : fileName(_fileName), secDelay(_secDelay), threaded("CShortLock", this) { }
  2399. ~CShortLock()
  2400. {
  2401. threaded.join();
  2402. }
  2403. virtual void threadmain() override
  2404. {
  2405. Owned<IDistributedFile> file=queryDistributedFileDirectory().lookup(fileName,nullptr,AccessMode::tbdRead,false,false,nullptr,defaultNonPrivilegedUser);
  2406. if (!file)
  2407. {
  2408. PROGLOG("File %s not found", fileName.get());
  2409. return;
  2410. }
  2411. PROGLOG("Locked file: %s, sleeping (before unlock) for %d secs", fileName.get(), secDelay);
  2412. MilliSleep(secDelay * 1000);
  2413. PROGLOG("Unlocking file: %s", fileName.get());
  2414. }
  2415. void start() { threaded.start(); }
  2416. };
  2417. void testDFSAddFailReAdd()
  2418. {
  2419. setupDFS(logctx, "addreadd");
  2420. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user); // disabled, auto-commit
  2421. logctx.CTXLOG("Creating super1 and supet2, adding sub1 and sub2 to super1 and sub3 to super2");
  2422. Owned<IDistributedSuperFile> sfile = dir.createSuperFile("regress::addreadd::super1", user, false, false, transaction);
  2423. sfile->addSubFile("regress::addreadd::sub1", false, NULL, false, transaction);
  2424. sfile->addSubFile("regress::addreadd::sub2", false, NULL, false, transaction);
  2425. sfile.clear();
  2426. Owned<IDistributedSuperFile> sfile2 = dir.createSuperFile("regress::addreadd::super2", user, false, false, transaction);
  2427. sfile2->addSubFile("regress::addreadd::sub3", false, NULL, false, transaction);
  2428. sfile2.clear();
  2429. /* Tests transaction failing, due to lock and retrying after having partial success */
  2430. CShortLock sL("regress::addreadd::sub2", 30); // the 2nd subfile of super1
  2431. sL.start();
  2432. transaction.setown(createDistributedFileTransaction(user)); // disabled, auto-commit
  2433. logctx.CTXLOG("Starting transaction");
  2434. transaction->start();
  2435. logctx.CTXLOG("Adding contents of regress::addreadd::super1 to regress::addreadd::super2, within transaction");
  2436. sfile.setown(transaction->lookupSuperFile("regress::addreadd::super2"));
  2437. sfile->addSubFile("regress::addreadd::super1", false, NULL, true, transaction); // add contents of super1 to super2
  2438. sfile.setown(transaction->lookupSuperFile("regress::addreadd::super1"));
  2439. sfile->removeSubFile(NULL, false, false, transaction); // clears super1
  2440. sfile.clear();
  2441. try
  2442. {
  2443. transaction->commit();
  2444. }
  2445. catch (IException *e)
  2446. {
  2447. StringBuffer eStr;
  2448. e->errorMessage(eStr);
  2449. CPPUNIT_ASSERT_MESSAGE(eStr.str(), 0);
  2450. e->Release();
  2451. }
  2452. transaction.clear();
  2453. sfile.setown(dir.lookupSuperFile("regress::addreadd::super2", user));
  2454. ASSERT(3 == sfile->numSubFiles() && "regress::addreadd::super2 should contain 3 subfiles");
  2455. ASSERT(NULL != sfile->querySubFileNamed("regress::addreadd::sub1") && "regress::addreadd::sub1, should be a subfile of super2");
  2456. ASSERT(NULL != sfile->querySubFileNamed("regress::addreadd::sub2") && "regress::addreadd::sub2, should be a subfile of super2");
  2457. ASSERT(NULL != sfile->querySubFileNamed("regress::addreadd::sub3") && "regress::addreadd::sub3, should be a subfile of super2");
  2458. sfile.setown(dir.lookupSuperFile("regress::addreadd::super1", user));
  2459. ASSERT(0 == sfile->numSubFiles() && "regress::addreadd::super1 should contain 0 subfiles");
  2460. }
  2461. void testDFSRetrySuperLock()
  2462. {
  2463. setupDFS(logctx, "retrysuperlock");
  2464. logctx.CTXLOG("Creating regress::retrysuperlock::super1 and regress::retrysuperlock::sub1");
  2465. Owned<IDistributedSuperFile> sfile = dir.createSuperFile("regress::retrysuperlock::super1", user, false, false);
  2466. sfile->addSubFile("regress::retrysuperlock::sub1", false, NULL, false);
  2467. sfile.clear();
  2468. /* Tests transaction failing, due to lock and retrying after having partial success */
  2469. CShortLock sL("regress::retrysuperlock::super1", 15);
  2470. sL.start();
  2471. sfile.setown(dir.lookupSuperFile("regress::retrysuperlock::super1", user));
  2472. if (sfile)
  2473. {
  2474. logctx.CTXLOG("Removing subfiles from regress::retrysuperlock::super1");
  2475. sfile->removeSubFile(NULL, false, false);
  2476. logctx.CTXLOG("SUCCEEDED");
  2477. }
  2478. // put it back, for next test
  2479. sfile->addSubFile("regress::retrysuperlock::sub1", false, NULL, false);
  2480. sfile.clear();
  2481. // try again, this time in transaction
  2482. Owned<IDistributedFileTransaction> transaction = createDistributedFileTransaction(user); // disabled, auto-commit
  2483. logctx.CTXLOG("Starting transaction");
  2484. transaction->start();
  2485. sfile.setown(transaction->lookupSuperFile("regress::retrysuperlock::super1"));
  2486. if (sfile)
  2487. {
  2488. logctx.CTXLOG("Removing subfiles from regress::retrysuperlock::super1 with transaction");
  2489. sfile->removeSubFile(NULL, false, false, transaction);
  2490. logctx.CTXLOG("SUCCEEDED");
  2491. }
  2492. sfile.clear();
  2493. logctx.CTXLOG("Committing transaction");
  2494. transaction->commit();
  2495. }
  2496. };
  2497. CPPUNIT_TEST_SUITE_REGISTRATION( CDaliDFSRetrySlowTests );
  2498. CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( CDaliDFSRetrySlowTests, "CDaliDFSRetrySlowTests" );
  2499. class CDaliUtils : public CppUnit::TestFixture
  2500. {
  2501. CPPUNIT_TEST_SUITE(CDaliUtils);
  2502. CPPUNIT_TEST(testDFSLfn);
  2503. CPPUNIT_TEST_SUITE_END();
  2504. public:
  2505. void testDFSLfn()
  2506. {
  2507. const char *lfns[] = { "~foreign::192.168.16.1::scope1::file1",
  2508. "~file::192.168.16.1::dir1::file1",
  2509. "~file::192.168.16.1::>some query or another",
  2510. "~file::192.168.16.1::wild?card1",
  2511. "~file::192.168.16.1::wild*card2",
  2512. "~file::192.168.16.1::^C^a^S^e^d",
  2513. "~file::192.168.16.1::file@cluster1",
  2514. "~prefix::{multi1*,multi2*}",
  2515. "{~foreign::192.168.16.1::multi1, ~foreign::192.168.16.2::multi2}",
  2516. // NB: CDfsLogicalFileName allows these with strict=false (the default)
  2517. ". :: scope1 :: file",
  2518. ":: scope1 :: file",
  2519. "~ scope1 :: scope2 :: file ",
  2520. ". :: scope1 :: file nine",
  2521. ". :: scope1 :: file ten ",
  2522. ". :: scope1 :: file",
  2523. NULL // terminator
  2524. };
  2525. const char *invalidLfns[] = {
  2526. ". :: sc~ope1::file",
  2527. ". :: ::file",
  2528. "~~scope1::file",
  2529. "~sc~ope1::file2",
  2530. ".:: scope1::file*",
  2531. NULL // terminator
  2532. };
  2533. const char *translatedLfns[][2] = {
  2534. { ".::fname", ".::fname" },
  2535. { "fname", ".::fname" },
  2536. { "~.::fname", ".::fname" },
  2537. { ".::.::.::fname", ".::fname" },
  2538. { ".::.::.::sname::.::.::fname", "sname::fname" },
  2539. { "~.::.::scope2::.::.::.::fname", "scope2::fname" },
  2540. { "{.::.::multitest1f1, .::.::multitest1f2}", "{.::multitest1f1,.::multitest1f2}" },
  2541. { ".::.::.::.sname.::.::.fname.", ".sname.::.fname." },
  2542. { "~foreign::192.168.16.1::.::scope1::file1", "foreign::192.168.16.1::scope1::file1" },
  2543. { "{~foreign::192.168.16.1::.::.::multi1, ~foreign::192.168.16.2::multi2::.::fname}", "{foreign::192.168.16.1::multi1,foreign::192.168.16.2::multi2::fname}" },
  2544. { nullptr, nullptr } // terminator
  2545. };
  2546. PROGLOG("Checking valid logical filenames");
  2547. unsigned nlfn=0;
  2548. for (;;)
  2549. {
  2550. const char *lfn = lfns[nlfn++];
  2551. if (NULL == lfn)
  2552. break;
  2553. PROGLOG("lfn = %s", lfn);
  2554. CDfsLogicalFileName dlfn;
  2555. try
  2556. {
  2557. dlfn.set(lfn);
  2558. }
  2559. catch (IException *e)
  2560. {
  2561. VStringBuffer err("Logical filename '%s' failed.", lfn);
  2562. EXCLOG(e, err.str());
  2563. e->Release();
  2564. CPPUNIT_FAIL(err.str());
  2565. }
  2566. }
  2567. PROGLOG("Checking translations");
  2568. nlfn = 0;
  2569. for (;;)
  2570. {
  2571. const char **entry = translatedLfns[nlfn++];
  2572. if (nullptr == entry[0])
  2573. break;
  2574. const char *lfn = entry[0];
  2575. const char *expected = entry[1];
  2576. PROGLOG("lfn = %s, expect = %s", lfn, expected);
  2577. CDfsLogicalFileName dlfn;
  2578. StringBuffer err;
  2579. try
  2580. {
  2581. dlfn.set(lfn);
  2582. const char *result = dlfn.get();
  2583. if (!streq(result, expected))
  2584. err.appendf("Logical filename '%s' should have translated to '%s', but result was '%s'.", lfn, expected, result);
  2585. }
  2586. catch (IException *e)
  2587. {
  2588. err.appendf("Logical filename '%s' failed: ", lfn);
  2589. e->errorMessage(err);
  2590. e->Release();
  2591. }
  2592. if (err.length())
  2593. {
  2594. ERRLOG("%s", err.str());
  2595. CPPUNIT_FAIL(err.str());
  2596. }
  2597. }
  2598. }
  2599. };
  2600. CPPUNIT_TEST_SUITE_REGISTRATION( CDaliUtils );
  2601. CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( CDaliUtils, "DaliUtils" );
  2602. class CFileNameNormalizeUnitTest : public CppUnit::TestFixture, CDfsLogicalFileName
  2603. {
  2604. CPPUNIT_TEST_SUITE(CFileNameNormalizeUnitTest);
  2605. CPPUNIT_TEST(testFileNameNormalize);
  2606. CPPUNIT_TEST_SUITE_END();
  2607. public:
  2608. void testFileNameNormalize()
  2609. {
  2610. //Columns
  2611. const int inFileName = 0;
  2612. const int normalizedFileName = 1;
  2613. const char *validExternalLfns[][2] = {
  2614. // input file name expected normalized file name
  2615. {"~file::192.168.16.1::dir1::file1", "file::192.168.16.1::dir1::file1"},
  2616. {"~ file:: 192.168.16.1::dir1::file1", "file::192.168.16.1::dir1::file1"},
  2617. {"~file::192.168.16.1::>some query or another", "file::192.168.16.1::>some query or another"},
  2618. {"~file::192.168.16.1::>Some Query or Another", "file::192.168.16.1::>Some Query or Another"},
  2619. {"~file::192.168.16.1::wild?card1", "file::192.168.16.1::wild?card1"},
  2620. {"~file::192.168.16.1::wild*card2", "file::192.168.16.1::wild*card2"},
  2621. {"~file::192.168.16.1::^C^a^S^e^d", "file::192.168.16.1::^c^a^s^e^d"},
  2622. {nullptr, nullptr } // terminator
  2623. };
  2624. const char *validInternalLfns[][2] = {
  2625. // input file name expected normalized file name
  2626. {"~foreign::192.168.16.1::scope1::file1", "foreign::192.168.16.1::scope1::file1"},
  2627. {".::scope1::file", "scope1::file"},
  2628. {"~ scope1 :: scope2 :: file ", "scope1::scope2::file"},
  2629. {". :: scope1 :: file nine", "scope1::file nine"},
  2630. {". :: scope1 :: file ten ", "scope1::file ten"},
  2631. {". :: scope1 :: file", "scope1::file"},
  2632. {"~scope1::file@cluster1", "scope1::file"},
  2633. {"~scope::^C^a^S^e^d", "scope::^c^a^s^e^d"},
  2634. {"~scope::CaSed", "scope::cased"},
  2635. {"~scope::^CaSed", "scope::^cased"},
  2636. {nullptr, nullptr} // terminator
  2637. };
  2638. // Check results
  2639. const bool externalFile = true;
  2640. const bool internalFile = false;
  2641. const bool fileNameMatch = true;
  2642. PROGLOG("Checking external filenames detection and normalization");
  2643. unsigned nlfn=0;
  2644. for (;;)
  2645. {
  2646. const char *lfn = validExternalLfns[nlfn][inFileName];
  2647. if (nullptr == lfn)
  2648. break;
  2649. PROGLOG("lfn = '%s'", lfn);
  2650. StringAttr res;
  2651. try
  2652. {
  2653. ASSERT(externalFile == normalizeExternal(lfn, res, false));
  2654. PROGLOG("res = '%s'", res.str());
  2655. ASSERT(fileNameMatch == streq(res.str(), validExternalLfns[nlfn][normalizedFileName]))
  2656. }
  2657. catch (IException *e)
  2658. {
  2659. VStringBuffer err("External filename '%s' ('%s') failed.", lfn, res.str());
  2660. EXCLOG(e, err.str());
  2661. e->Release();
  2662. CPPUNIT_FAIL(err.str());
  2663. }
  2664. nlfn++;
  2665. }
  2666. PROGLOG("Checking valid internal filenames");
  2667. nlfn=0;
  2668. for (;;)
  2669. {
  2670. const char *lfn = validInternalLfns[nlfn][inFileName];
  2671. if (nullptr == lfn)
  2672. break;
  2673. PROGLOG("lfn = '%s'", lfn);
  2674. StringAttr res;
  2675. try
  2676. {
  2677. ASSERT(internalFile == normalizeExternal(lfn, res, false));
  2678. normalizeName(lfn, res, false);
  2679. PROGLOG("res = '%s'", res.str());
  2680. ASSERT(fileNameMatch == streq(res.str(), validInternalLfns[nlfn][normalizedFileName]))
  2681. }
  2682. catch (IException *e)
  2683. {
  2684. VStringBuffer err("Internal filename '%s' ('%s') failed.", lfn, res.str());
  2685. EXCLOG(e, err.str());
  2686. e->Release();
  2687. CPPUNIT_FAIL(err.str());
  2688. }
  2689. nlfn++;
  2690. }
  2691. }
  2692. };
  2693. CPPUNIT_TEST_SUITE_REGISTRATION( CFileNameNormalizeUnitTest );
  2694. CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( CFileNameNormalizeUnitTest, "CFileNameNormalizeUnitTest" );
  2695. #endif // _USE_CPPUNIT