@@ -35,10 +35,13 @@ const request = require("@arangodb/request");
3535const tasks = require ( "@arangodb/tasks" ) ;
3636
3737const arango = internal . arango ;
38- const compareTicks = require ( "@arangodb/replication" ) . compareTicks ;
39- const wait = internal . wait ;
4038const db = internal . db ;
39+ const fs = require ( 'fs' ) ;
40+ const path = require ( 'path' ) ;
41+ const utils = require ( '@arangodb/foxx/manager-utils' ) ;
42+ const wait = internal . wait ;
4143
44+ const compareTicks = require ( "@arangodb/replication" ) . compareTicks ;
4245const suspendExternal = internal . suspendExternal ;
4346const continueExternal = internal . continueExternal ;
4447
@@ -293,10 +296,136 @@ function waitUntilHealthStatusIs(isHealthy, isFailed) {
293296 return false ;
294297}
295298
299+ function loadFoxxIntoZip ( path ) {
300+ let zip = utils . zipDirectory ( path ) ;
301+ let content = fs . readFileSync ( zip ) ;
302+ fs . remove ( zip ) ;
303+ return {
304+ type : 'inlinezip' ,
305+ buffer : content
306+ } ;
307+ }
308+ function checkFoxxService ( readOnly ) {
309+ const onlyJson = {
310+ 'accept' : 'application/json' ,
311+ 'accept-content-type' : 'application/json'
312+ } ;
313+ let reply ;
314+ db . _useDatabase ( "_system" ) ;
315+
316+ [
317+ '/_db/_system/_admin/aardvark/index.html' ,
318+ '/_db/_system/itz/index' ,
319+ '/_db/_system/crud/xxx'
320+ ] . forEach ( route => {
321+ for ( let i = 0 ; i < 200 ; i ++ ) {
322+ try {
323+ reply = arango . GET_RAW ( route , onlyJson ) ;
324+ if ( reply . code === 200 ) {
325+ print ( route + " OK" ) ;
326+ return ;
327+ }
328+ let msg = JSON . stringify ( reply ) ;
329+ if ( reply . hasOwnProperty ( 'parsedBody' ) ) {
330+ msg = " '" + reply . parsedBody . errorNum + "' - " + reply . parsedBody . errorMessage ;
331+ }
332+ print ( route + " Not yet ready, retrying: " + msg ) ;
333+ } catch ( e ) {
334+ print ( route + " Caught - need to retry. " + JSON . stringify ( e ) ) ;
335+ }
336+ internal . sleep ( 3 ) ;
337+ }
338+ throw ( "foxx route '" + route + "' not ready on time!" ) ;
339+ } ) ;
340+
341+ print ( "Foxx: Itzpapalotl getting the root of the gods" ) ;
342+ reply = arango . GET_RAW ( '/_db/_system/itz' ) ;
343+ assertEqual ( reply . code , "307" , JSON . stringify ( reply ) ) ;
344+
345+ print ( 'Foxx: Itzpapalotl getting index html with list of gods' ) ;
346+ reply = arango . GET_RAW ( '/_db/_system/itz/index' ) ;
347+ assertEqual ( reply . code , "200" , JSON . stringify ( reply ) ) ;
348+
349+ print ( "Foxx: Itzpapalotl summoning Chalchihuitlicue" ) ;
350+ reply = arango . GET_RAW ( '/_db/_system/itz/Chalchihuitlicue/summon' , onlyJson ) ;
351+ assertEqual ( reply . code , "200" , JSON . stringify ( reply ) ) ;
352+ let parsedBody = JSON . parse ( reply . body ) ;
353+ assertEqual ( parsedBody . name , "Chalchihuitlicue" ) ;
354+ assertTrue ( parsedBody . summoned ) ;
355+
356+ print ( "Foxx: crud testing get xxx" ) ;
357+ reply = arango . GET_RAW ( '/_db/_system/crud/xxx' , onlyJson ) ;
358+ assertEqual ( reply . code , "200" ) ;
359+ parsedBody = JSON . parse ( reply . body ) ;
360+ assertEqual ( parsedBody , [ ] ) ;
361+
362+ print ( "Foxx: crud testing POST xxx" ) ;
363+
364+ reply = arango . POST_RAW ( '/_db/_system/crud/xxx' , { _key : "test" } ) ;
365+ if ( readOnly ) {
366+ assertEqual ( reply . code , "400" ) ;
367+ } else {
368+ assertEqual ( reply . code , "201" ) ;
369+ }
370+
371+ print ( "Foxx: crud testing get xxx" ) ;
372+ reply = arango . GET_RAW ( '/_db/_system/crud/xxx' , onlyJson ) ;
373+ assertEqual ( reply . code , "200" ) ;
374+ parsedBody = JSON . parse ( reply . body ) ;
375+ if ( readOnly ) {
376+ assertEqual ( parsedBody , [ ] ) ;
377+ } else {
378+ assertEqual ( parsedBody . length , 1 ) ;
379+ }
380+
381+ print ( 'Foxx: crud testing delete document' ) ;
382+ reply = arango . DELETE_RAW ( '/_db/_system/crud/xxx/' + 'test' ) ;
383+ if ( readOnly ) {
384+ assertEqual ( reply . code , "400" ) ;
385+ } else {
386+ assertEqual ( reply . code , "204" ) ;
387+ }
388+ }
389+
390+ function installFoxx ( mountpoint , which , mode ) {
391+ let headers = { } ;
392+ let content ;
393+ if ( which . type === 'js' ) {
394+ headers [ 'content-type' ] = 'application/javascript' ;
395+ content = which . buffer ;
396+ } else if ( which . type === 'dir' ) {
397+ headers [ 'content-type' ] = 'application/zip' ;
398+ var utils = require ( '@arangodb/foxx/manager-utils' ) ;
399+ let zip = utils . zipDirectory ( which . buffer ) ;
400+ content = fs . readFileSync ( zip ) ;
401+ fs . remove ( zip ) ;
402+ } else if ( which . type === 'inlinezip' ) {
403+ content = which . buffer ;
404+ headers [ 'content-type' ] = 'application/zip' ;
405+ } else if ( which . type === 'url' ) {
406+ content = { source : which } ;
407+ } else if ( which . type === 'file' ) {
408+ content = fs . readFileSync ( which . buffer ) ;
409+ }
410+ let devmode = '' ;
411+ if ( typeof which . devmode === "boolean" ) {
412+ devmode = `&development=${ which . devmode } ` ;
413+ }
414+ let crudResp ;
415+ if ( mode === "upgrade" ) {
416+ crudResp = arango . PATCH ( '/_api/foxx/service?mount=' + mountpoint + devmode , content , headers ) ;
417+ } else if ( mode === "replace" ) {
418+ crudResp = arango . PUT ( '/_api/foxx/service?mount=' + mountpoint + devmode , content , headers ) ;
419+ } else {
420+ crudResp = arango . POST ( '/_api/foxx?mount=' + mountpoint + devmode , content , headers ) ;
421+ }
422+ expect ( crudResp ) . to . have . property ( 'manifest' ) ;
423+ return crudResp ;
424+ }
425+
296426// Testsuite that quickly checks some of the basic premises of
297427// the active failover functionality. It is designed as a quicker
298428// variant of the node resilience tests (for active failover).
299- // Things like Foxx resilience are not tested
300429function ActiveFailoverSuite ( ) {
301430 let servers = getClusterEndpoints ( ) ;
302431 assertTrue ( servers . length >= 4 , "This test expects four single instances" ) ;
@@ -370,37 +499,76 @@ function ActiveFailoverSuite() {
370499 // Simple failover case: Leader is suspended, slave needs to
371500 // take over within a reasonable amount of time
372501 testFailover : function ( ) {
502+ const itzpapalotlPath = path . resolve ( internal . pathForTesting ( 'common' ) , 'test-data' , 'apps' , 'itzpapalotl' ) ;
503+ const itzpapalotlZip = loadFoxxIntoZip ( itzpapalotlPath ) ;
504+ installFoxx ( "/itz" , itzpapalotlZip ) ;
505+
506+ const minimalWorkingServicePath = path . resolve ( internal . pathForTesting ( 'common' ) , 'test-data' , 'apps' , 'crud' ) ;
507+ const minimalWorkingZip = loadFoxxIntoZip ( minimalWorkingServicePath ) ;
508+ installFoxx ( '/crud' , minimalWorkingZip ) ;
373509
510+ checkFoxxService ( false ) ;
374511 assertTrue ( checkInSync ( currentLead , servers ) ) ;
375512 assertEqual ( checkData ( currentLead ) , 10000 ) ;
376513
377- suspended = instanceinfo . arangods . filter ( arangod => arangod . endpoint === currentLead ) ;
378- suspended . forEach ( arangod => {
379- print ( "Suspending Leader: " , arangod . endpoint ) ;
380- assertTrue ( suspendExternal ( arangod . pid ) ) ;
381- } ) ;
382-
514+ let suspended ;
383515 let oldLead = currentLead ;
384- // await failover and check that follower get in sync
385- currentLead = checkForFailover ( currentLead ) ;
386- assertNotEqual ( currentLead , oldLead ) ;
387- print ( "Failover to new leader : " , currentLead ) ;
388-
389- internal . wait ( 5 ) ; // settle down, heartbeat interval is 1s
390- assertEqual ( checkData ( currentLead ) , 10000 ) ;
391- print ( "New leader has correct data" ) ;
392-
393- // check the remaining followers get in sync
394- assertTrue ( checkInSync ( currentLead , servers , oldLead ) ) ;
395-
396- // restart the old leader
397- suspended . forEach ( arangod => {
398- print ( "Resuming: " , arangod . endpoint ) ;
399- assertTrue ( continueExternal ( arangod . pid ) ) ;
400- } ) ;
401- suspended = [ ] ;
402-
403- assertTrue ( checkInSync ( currentLead , servers ) ) ;
516+ try {
517+ suspended = instanceinfo . arangods . filter ( arangod => arangod . endpoint === currentLead ) ;
518+ suspended . forEach ( arangod => {
519+ print ( "Suspending Leader: " , arangod . endpoint ) ;
520+ assertTrue ( suspendExternal ( arangod . pid ) ) ;
521+ } ) ;
522+
523+ // await failover and check that follower get in sync
524+ currentLead = checkForFailover ( currentLead ) ;
525+ assertNotEqual ( currentLead , oldLead ) ;
526+ print ( "Failover to new leader : " , currentLead ) ;
527+
528+ internal . wait ( 5 ) ; // settle down, heartbeat interval is 1s
529+ assertEqual ( checkData ( currentLead ) , 10000 ) ;
530+ print ( "New leader has correct data" ) ;
531+
532+ // check the remaining followers get in sync
533+ assertTrue ( checkInSync ( currentLead , servers , oldLead ) ) ;
534+
535+ connectToServer ( currentLead ) ;
536+ checkFoxxService ( false ) ;
537+
538+ } finally {
539+ // restart the old leader
540+ suspended . forEach ( arangod => {
541+ print ( "Resuming: " , arangod . endpoint ) ;
542+ assertTrue ( continueExternal ( arangod . pid ) ) ;
543+ } ) ;
544+ assertTrue ( checkInSync ( currentLead , servers ) ) ;
545
38AC
+ // after its in sync, halt all others so it becomes the leader again
546+ suspended = instanceinfo . arangods . filter ( arangod =>
547+ ( arangod . endpoint !== oldLead ) && ( arangod . role === 'single' ) ) ;
548+ suspended . forEach ( arangod => {
549+ print ( "Suspending all but old Leader: " , arangod . endpoint ) ;
550+ assertTrue ( suspendExternal ( arangod . pid ) ) ;
551+ } ) ;
552+ currentLead = checkForFailover ( currentLead ) ;
553+ assertEqual ( currentLead , oldLead ) ;
554+ connectToServer ( currentLead ) ;
555+ // restart the other followers so the system is all up and running again
556+ suspended . forEach ( arangod => {
557+ print ( "Resuming: " , arangod . endpoint ) ;
558+ assertTrue ( continueExternal ( arangod . pid ) ) ;
559+ } ) ;
560+ assertTrue ( checkInSync ( currentLead , servers ) ) ;
561+ let stati = [ ] ;
562+ [ "/itz" , "/crud" ] . forEach ( mount => {
563+ try {
564+ print ( "Uninstalling " + mount ) ;
565+ let res = arango . DELETE (
566+ "/_db/_system/_admin/aardvark/foxxes?teardown=true&mount=" + mount ) ;
567+ stati . push ( res . error ) ;
568+ } catch ( e ) { }
569+ } ) ;
570+ assertEqual ( stati , [ false , false ] ) ;
571+ }
404572 } ,
405573
406574 // More complex case: We want to get the most up to date follower
0 commit comments