File: 1.19.1b/server/bin/tester.php (View as HTML)

  1: <?php
  2: /* -------------------------------------------------------------
  3: This file is part of FreeNATS
  4: 
  5: FreeNATS is (C) Copyright 2008-2017 PurplePixie Systems
  6: 
  7: FreeNATS is free software: you can redistribute it and/or modify
  8: it under the terms of the GNU General Public License as published by
  9: the Free Software Foundation, either version 3 of the License, or
 10: (at your option) any later version.
 11: 
 12: FreeNATS is distributed in the hope that it will be useful,
 13: but WITHOUT ANY WARRANTY; without even the implied warranty of
 14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15: GNU General Public License for more details.
 16: 
 17: You should have received a copy of the GNU General Public License
 18: along with FreeNATS.  If not, see www.gnu.org/licenses
 19: 
 20: For more information see www.purplepixie.org/freenats
 21: -------------------------------------------------------------- */
 22: 
 23: if ((isset($argc))&&(isset($argv))) // specific node or all nodes
 24: 	{
 25: 	if ($argc>1)
 26: 		{
 27: 		$nfilter=$argv[1];
 28: 		}
 29: 	else $nfilter="";
 30: 	}
 31: 
 32: require("include.php");
 33: 
 34: $dbt="";
 35: 
 36: function db($txt,$nl=true) // debug text
 37: {
 38: global $dbt;
 39: echo $txt;
 40: $dbt.=$txt;
 41: if ($nl) 
 42: 	{
 43: 	echo "\n";
 44: 	$dbt.=" <br>\n";
 45: 	}
 46: }
 47: 
 48: $NATS->Start();
 49: if ($nfilter!="") $st=": Node ".$nfilter;
 50: else $st="";
 51: db("NATS Tester Script Starting".$st);
 52: 
 53: $suspend = $NATS->Cfg->Get("site.tester.suspended",0);
 54: 
 55: if ($suspend != 1)
 56: {
 57: 
 58: 	$highalertlevel=-1;
 59: 	$talertc=0;
 60: 
 61: 	// check if already running
 62: 	$still_running=false;
 63: 	$cq="SELECT trid,startx FROM fntestrun WHERE fnode=\"".ss($nfilter)."\" AND finishx=0 LIMIT 0,1";
 64: 	$cr=$NATS->DB->Query($cq);
 65: 
 66: 	if ($runrow=$NATS->DB->Fetch_Array($cr))
 67: 		{ // yes there is a testrun session for this node(s)
 68: 		$timelimit=$NATS->Cfg->Get("test.session.limit",60*60);
 69: 		if ( (!is_numeric($timelimit)) || ($timelimit<0) ) $timelimit=60*60; // bogus config value
 70: 		
 71: 		// n.b. a timelimit of 0 means the session will never expire so...
 72: 		if ( ($timelimit>0) && ((time()-$runrow['startx'])>$timelimit) )
 73: 			{
 74: 			// valid time limit and the difference is more than the limit so close it
 75: 			$uq="UPDATE fntestrun SET finishx=1 WHERE trid=".$runrow['trid'];
 76: 			$NATS->DB->Query($uq);
 77: 			if ($NATS->DB->Affected_Rows()>0) 
 78: 				{
 79: 				$NATS->Event("Tester Already Running: Cleared",3,"Tester","Stale");
 80: 				db("Tester Already Running: Cleared");
 81: 				}
 82: 			else $NATS->Event("Tester Already Running: Failed to Clear",1,"Tester","Stale"); // weirdness... run anyway
 83: 			}
 84: 		else $still_running=true; // either never timesout or newer than timelimit ago
 85: 		}
 86: 	$NATS->DB->Free($cr);
 87: 
 88: 	// and if it is then don't continue
 89: 	if ($still_running)
 90: 		{
 91: 		$NATS->Event("Tester Already Running: Aborted",1,"Tester","Error");
 92: 		db("Tester Already Running: Aborted");
 93: 		$NATS->Stop();
 94: 		exit();
 95: 		}
 96: 
 97: 
 98: 	$gq="INSERT INTO fntestrun(startx,fnode) VALUES(".time().",\"".ss($nfilter)."\")";
 99: 	$NATS->DB->Query($gq);
100: 	$trid=$NATS->DB->Insert_Id();
101: 	db("Test ID: ".$trid." (Started at ".nicedt(time()).")");
102: 	$NATS->Event("Tester ".$trid." Started",5,"Tester","Start");
103: 
104: 	db(" ");
105: 
106: 	// Find node to test - must be enabled, have id if set, and be due to be tested (nextrunx)
107: 
108: 	$q="SELECT * FROM fnnode WHERE nodeenabled=1";
109: 	if ($nfilter!="") $q.=" AND nodeid=\"".ss($nfilter)."\"";
110: 	$q.=" AND nextrunx<=".time();
111: 
112: 	$r=$NATS->DB->Query($q);
113: 
114: 
115: 	while ($row=$NATS->DB->Fetch_Array($r))
116: 		{
117: 		$dotests=true;
118: 		$alertlevel=0;
119: 		$alerts=array();
120: 		$alertc=0;
121: 		db("NodeID: ".$row['nodeid']);
122: 		$NATS->Event("Tester ".$trid." Node ".$row['nodeid'],10,"Tester","Node");
123: 
124: 		// Scheduling Test In Here - sets dotests to false and alertlevel to -1 untested
125: 		if ($row['scheduleid']!=0) // has a schedule
126: 			{
127: 			db(" Has Schedule: Yes - Checking");
128: 			$run=run_x_in_schedule(time(),$row['scheduleid']);
129: 			if (!$run)
130: 				{
131: 				db(" In Schedule: No - Skipping Tests");
132: 				$NATS->Event("Tester ".$trid." Skipped by Schedule",5,"Tester","Node");
133: 				$dotests=false;
134: 				$alertlevel=-1;
135: 				}
136: 			else db(" In Schedule: Yes");
137: 			}
138: 		
139: 		$eventdata=array( "nodeid" => $row['nodeid'], "in_schedule" => $dotests );
140: 		$NATS->EventHandler("node_test_start",$eventdata);
141: 		
142: 		
143: 		$ptr=0;
144: 		$pal=0;
145: 		
146: 
147: 		// Update lastrun and nextrun regardless of dotests
148: 		$q="UPDATE fnnode SET lastrunx=".time().",nextrunx=".next_run_x($row['testinterval'])." WHERE nodeid=\"".ss($row['nodeid'])."\"";
149: 		$NATS->DB->Query($q);
150: 
151: 		$pingpassed=false; // this will only be set to true if a test is done and passes - for the "child" nodes
152: 		if ($row['pingtest']&&$dotests)
153: 			{
154: 			db(" Ping Test: Yes");
155: 			$NATS->Event("Tester ".$trid." Pinging Node ".$row['nodeid'],10,"Tester","Ping");
156: 			$ptr=PingTest($row['hostname']);
157: 			$NATS->Event("Tester ".$trid." Ping Node ".$row['nodeid']." Returned ".$ptr,10,"Tester","Ping");
158: 			db(" Ping Returned: ".$ptr);
159: 			if ( ($ptr<=0) && ($NATS->Cfg->Get("test.icmp.attempts","2")>1) )
160: 				{
161: 				$att=$NATS->Cfg->Get("test.icmp.attempts","2");
162: 				for ($a=2; $a<=$att; $a++) // starting on second attempt
163: 					{
164: 					// try again...
165: 					test_sleep();
166: 					db(" Trying Ping Again - X".$a);
167: 					$NATS->Event("Tester ".$trid." Ping X".$a." Node ".$row['nodeid'],10,"Tester","Ping");
168: 					$ptr=PingTest($row['hostname']);
169: 					$NATS->Event("Tester ".$trid." Ping Node ".$row['nodeid']." Returned ".$ptr,10,"Tester","Ping");
170: 					db(" Ping Returned: ".$ptr);
171: 					if ($ptr>0) $a=$att+1; // break out of the loop
172: 					}
173: 				}
174: 				
175: 			if ($ptr<=0) 
176: 				{
177: 				$alertlevel=2;
178: 				db(" Ping Test: Failed");
179: 				$alerts[$alertc++]="ping failed";
180: 				$pal=2;
181: 				}
182: 			else 
183: 				{
184: 				db(" Ping Test: Passed");
185: 				$pingpassed=true;
186: 				}
187: 			
188: 			// pingtest output bodge
189: 			// is there a test entry for ICMP
190: 			$fq="SELECT localtestid FROM fnlocaltest WHERE nodeid=\"".$row['nodeid']."\" AND testtype=\"ICMP\"";
191: 			$fr=$NATS->DB->Query($fq);
192: 			$ltid_icmp="";
193: 			if ($irow=$NATS->DB->Fetch_Array($fr))
194: 				{ // exists
195: 				$uq="UPDATE fnlocaltest SET alertlevel=".$pal.",lastrunx=".time().",lastvalue=".$ptr.",testrecord=1,testinterval=0 WHERE localtestid=".$irow['localtestid'];
196: 				$ltid_icmp=$irow['localtestid'];
197: 				//echo $uq;
198: 				$NATS->DB->Query($uq);
199: 				}
200: 			else
201: 				{ // doesn't exist
202: 				$uq="INSERT INTO fnlocaltest(nodeid,testrecord,testinterval,testtype,alertlevel,lastrunx,lastvalue) VALUES(\"".$row['nodeid']."\",1,0,\"ICMP\",".$pal.",".time().",".$ptr.")";
203: 				//echo $uq;
204: 				$NATS->DB->Query($uq);
205: 				$ltid_icmp=$NATS->DB->Insert_Id();
206: 				}
207: 			$NATS->DB->Free($fr);
208: 			
209: 			// record the ICMP bodge-test here
210: 			$rq="INSERT INTO fnrecord(testid,recordx,testvalue,alertlevel,nodeid) VALUES(\"L".$ltid_icmp."\",".time().",".$ptr.",".$pal.",\"".$row['nodeid']."\")";
211: 			$NATS->DB->Query($rq);
212: 			//echo $rq." ".$NATS->DB->Affected_Rows()."\n";
213: 			
214: 			}
215: 		else
216: 			{ // further ICMP bodge - update to -1 or do nothing if the test doesn't exist
217: 			$uq="UPDATE fnlocaltest SET alertlevel=-1,lastrunx=".time()." WHERE nodeid=\"".$row['nodeid']."\" AND testtype=\"ICMP\"";
218: 			$NATS->DB->Query($uq);
219: 			}
220: 
221: 		if ($dotests&&($row['pingfatal'])&&($ptr<=0))
222: 			{
223: 			db(" Ping Fatal: Yes - Not Continuing");
224: 			$NATS->Event("Tester ".$trid." Ping Fatal for Node ".$row['nodeid'],10,"Tester","Ping");
225: 			$dotests=false;
226: 			}
227: 
228: 		 	// do the tests - only actually exec if dotests true
229: 
230: 		 	$first_test=true;
231: 		 	
232: 			db("Doing Local Tests");
233: 			$NATS->Event("Tester ".$trid." Testing Node ".$row['nodeid'],10,"Tester","Test");
234: 			$q="SELECT * FROM fnlocaltest WHERE nodeid=\"".$row['nodeid']."\" AND testtype!=\"ICMP\" AND testenabled=1 ORDER BY localtestid ASC";
235: 			$res=$NATS->DB->Query($q);
236: 			while ($lrow=$NATS->DB->Fetch_Array($res))
237: 				{
238: 				if ($lrow['nextrunx']<=time()) $testdue=true;
239: 				else $testdue=false;
240: 					
241: 				if ($first_test)
242: 					{
243: 					$first_test=false;
244: 					if ($row['pingtest']==1) test_sleep(); // sleep if has done a ping
245: 					}
246: 				else test_sleep();
247: 				
248: 				if ($testdue)
249: 					{
250: 				
251: 					$eventdata=array("nodeid"=>$row['nodeid'],"testid"=>"L".$lrow['testparam'],"testtype"=>$lrow['testtype']);
252: 					$NATS->EventHandler("localtest_start",$eventdata);
253: 						
254: 					db(" Test: ".$lrow['testtype']." (".$lrow['testparam'].")");
255: 					
256: 					// Build parameter array
257: 					$params=array();
258: 					$params[0]=$lrow['testparam']; // pass standard param in as 0
259: 					for ($a=1; $a<10; $a++)
260: 						{
261: 						$parstr="testparam".$a;
262: 						$params[$a]=$lrow[$parstr];
263: 						}
264: 					
265: 					if ($dotests)
266: 						{
267: 						$NATS->Event("Tester ".$trid." Node ".$row['nodeid']." Doing ".$lrow['testtype']."(".$lrow['testparam'].")",10,"Tester","Test");
268: 						$result=DoTest($lrow['testtype'],$lrow['testparam'],$row['hostname'],$lrow['timeout'],$params,$row['nodeid']);
269: 						$NATS->Event("Tester ".$trid." Node ".$row['nodeid']." Result ".$result." from ".$lrow['testtype']."(".$lrow['testparam'].")",10,"Tester","Test");
270: 						db(" Result: ".$result);
271: 						}
272: 					else $result=0;
273: 					
274: 					if ($dotests)
275: 					{
276: 					// evaluation
277: 					if ($lrow['simpleeval']==1) $lvl=SimpleEval($lrow['testtype'],$result);
278: 					else $lvl=nats_eval("L".$lrow['localtestid'],$result);
279: 					db(" Eval: ".$lvl);
280: 					
281: 					// put in the custom retries based on attempts here - we KNOW dotests is on so don't need to worry about untested status
282: 					$att=$lrow['attempts'];
283: 					if ( ($lvl!=0) && (is_numeric($att)) && ($att>1) )
284: 						{
285: 						for ($a=2; $a<=$att; $a++)
286: 							{
287: 							test_sleep();
288: 							db(" Test: ".$lrow['testtype']." (".$lrow['testparam'].") X".$a);
289: 							$NATS->Event("Tester ".$trid." Node ".$row['nodeid']." X".$a." Doing ".$lrow['testtype']."(".$lrow['testparam'].")",10,"Tester","Test");
290: 							$result=DoTest($lrow['testtype'],$lrow['testparam'],$row['hostname'],$lrow['timeout'],$params,$row['nodeid']);
291: 							db(" Result: ".$result);
292: 							if ($lrow['simpleeval']==1) $lvl=SimpleEval($lrow['testtype'],$result);
293: 							else $lvl=nats_eval("L".$lrow['localtestid'],$result);
294: 							db(" Eval: ".$lvl);
295: 							if ($lvl==0) $a=$att+1; // test passed
296: 							}
297: 						}
298: 					
299: 					// $lvl is now the last lvl regardless of where it came from
300: 							
301: 					if ($lvl>$alertlevel) $alertlevel=$lvl;
302: 					if ($lvl>0)
303: 						{
304: 						if ($lrow['testname']=="") $s=$lrow['testtype']."/".substr($lrow['testparam'],0,5)." ";
305: 						else $s=$lrow['testname']." ";
306: 						/*
307: 						if ($lvl>1) $s.=$NATS->Cfg->Get("site.text.failed","failed");
308: 						else $s.=$NATS->Cfg->Get("site.text.warning","warning");
309: 						*/
310: 						$s.=oText($lvl);
311: 						// site.alert.showvalue -- includes value in alert messages if numeric
312: 						// site.alert.showtext -- includes value in alert messages if textual
313: 						if (is_numeric($result))
314: 							{
315: 							if ($NATS->Cfg->Get("site.alert.showvalue",0)==1) $s.=" (".$result.")";
316: 							}
317: 						else // non-numeric
318: 							{
319: 							if ($NATS->Cfg->Get("site.alert.showtext",0)==1) $s.=" (".$result.")";
320: 							}
321: 						$alerts[$alertc++]=$s;
322: 						}
323: 					} else $lvl=-1;
324: 						
325: 					// record it
326: 					if ($lrow['testrecord']==1)
327: 						{
328: 						$tid="L".$lrow['localtestid'];
329: 						$iq="INSERT INTO fnrecord(testid,nodeid,alertlevel,testvalue,recordx) VALUES(";
330: 						$iq.="\"".$tid."\",\"".$row['nodeid']."\",".$lvl.",".$result.",".time().")";
331: 						$NATS->DB->Query($iq);
332: 						db(" Recording Test");
333: 						}
334: 					if ((!isset($result))||(!is_numeric($result))) $result=0; // safety net
335: 						
336: 					// update localtest record
337: 					$uq="UPDATE fnlocaltest SET lastrunx=".time().",nextrunx=".next_run_x($lrow['testinterval']).",alertlevel=".$lvl.",lastvalue=".$result." WHERE localtestid=".$lrow['localtestid'];
338: 					$NATS->DB->Query($uq);
339: 					
340: 					$eventdata=array("nodeid"=>$row['nodeid'],"testid"=>"L".$lrow['testparam'],"testtype"=>$lrow['testtype'],"alertlevel"=>$lvl);
341: 					$NATS->EventHandler("localtest_finish",$eventdata);
342: 					}
343: 					
344: 				else // test not due so take pre-existing level for it
345: 					{
346: 					$lvl=$lrow['alertlevel'];
347: 					if (($lvl>0)&&($lvl>$alertlevel)) $alertlevel=$lvl;
348: 					}
349: 				
350: 				
351: 				}
352: 				
353: 		// Node-side testy magic
354: 		db("Nodeside Testing");
355: 		$freshdata=false;
356: 		if ( $dotests && ($row['nsenabled']==1) && ($row['nspullenabled']==1) ) // should be doing a pull
357: 			{
358: 			$pullalert=$row['nspullalert']; // what happened the last time we tried
359: 			
360: 			if ($row['nsnextx']<=time()) // the time is right
361: 				{
362: 				db(" Pulling Data");
363: 				$pull_result=$NATS->Nodeside_Pull($row['nodeid']);
364: 				
365: 				if ($pull_result===false) // Pull Failed
366: 					{
367: 					db(" Pull Failed");
368: 					$pullalert=1; // alert
369: 					$alerts[$alertc++]="pull failed";
370: 					$alertlevel=2;
371: 					}
372: 				else // Pull Worked
373: 					{
374: 					$freshdata=true;
375: 					$pullalert=0; // ok
376: 					db(" Pull Succeeded");
377: 					}
378: 					
379: 					
380: 				db(" Updating Pull nslast/nextx and nspullalert");
381: 				$uq="UPDATE fnnode SET nsnextx=".next_run_x($row['nsinterval']).",nspullalert=".$pullalert.",nslastx=".time()." WHERE nodeid=\"".$row['nodeid']."\"";
382: 				$NATS->DB->Query($uq);
383: 				if ($NATS->DB->Affected_Rows()<=0) db(" - Failed");
384: 				}
385: 			/*
386: 			// Process for alerts in here - whether pulled or not!
387: 			$tq="SELECT testtype,testname,alertlevel FROM fnnstest WHERE nodeid=\"".$row['nodeid']."\" AND testenabled=1 AND testalerts=1 AND alertlevel>0";
388: 			$tr=$NATS->DB->Query($tq);
389: 			while ($trow=$NATS->DB->Fetch_Array($tr))
390: 				{
391: 				if ($trow['testname']=="") $tname=$trow['testtype'];
392: 				else $tname=$trow['testname'];
393: 				if ($freshdata) $alerts[$alertc++]=$tname." ".oText($trow['alertlevel']); // only record text to log if fresh
394: 				if ($trow['alertlevel']>$alertlevel) $alertlevel=$trow['alertlevel'];
395: 				}
396: 			*/
397: 				
398: 			// and finally again use pullalert - this is either the new value if a pull was attempted or just remains the same as the old one
399: 			// if pull not scheduled yet
400: 			if ($pullalert>0) $alertlevel=2; // so mark a failure
401: 
402: 			}
403: 			
404: 		if ( ($dotests && ($row['nsenabled']==1) && ($row['nspullenabled']==1)) ||		// pull and tests are on
405: 			(($row['nsenabled']==1)&&($row['nspushenabled']==1)) )	// or pushed
406: 			{
407: 			if ($row['nsfreshpush']==1)
408: 				{
409: 				$freshdata=true;
410: 				$uq="UPDATE fnnode SET nsfreshpush=0 WHERE nodeid=\"".$row['nodeid']."\"";
411: 				$NATS->DB->Query($uq);
412: 				}
413: 			// Process for alerts in here - whether pulled or not!
414: 			$tq="SELECT testtype,testname,alertlevel,lastvalue FROM fnnstest WHERE nodeid=\"".$row['nodeid']."\" AND testenabled=1 AND testalerts=1 AND alertlevel>0";
415: 			$tr=$NATS->DB->Query($tq);
416: 			while ($trow=$NATS->DB->Fetch_Array($tr))
417: 				{
418: 				if ($trow['testname']=="") $tname=$trow['testtype'];
419: 				else $tname=$trow['testname'];
420: 				if ($freshdata) // only record text to log if fresh
421: 					{
422: 					$s=$tname." ".oText($trow['alertlevel']); 
423: 					$result=$trow['lastvalue'];
424: 					if (is_numeric($result))
425: 							{
426: 							if ($NATS->Cfg->Get("site.alert.showvalue",0)==1) $s.=" (".$result.")";
427: 							}
428: 						else // non-numeric
429: 							{
430: 							if ($NATS->Cfg->Get("site.alert.showtext",0)==1) $s.=" (".$result.")";
431: 							}
432: 					$alerts[$alertc++]=$s;
433: 					}
434: 				if ($trow['alertlevel']>$alertlevel) $alertlevel=$trow['alertlevel'];
435: 				}
436: 			}
437: 				
438: 		$NATS->Event("Tester ".$trid." Finished Node ".$row['nodeid'],10,"Tester","Node");
439: 
440: 		$eventdata=array( "nodeid" => $row['nodeid'], "alertlevel" => $alertlevel );
441: 		$NATS->EventHandler("node_test_finish",$eventdata);
442: 
443: 		db("Highest Alert Level: ".$alertlevel);
444: 		db("Alert Count        : ".$alertc);
445: 		$als="";
446: 		foreach($alerts as $al) $als.=$al.", ";
447: 		db("Alerts: ".$als);
448: 
449: 		$NATS->SetAlerts($row['nodeid'],$alertlevel,$alerts);
450: 		
451: 		// This is where child/slave nodes spawn
452: 		
453: 		// $pingpassed bool holds if pingtest has passed
454: 		// $alertlevel holds the overall status
455: 		$chq="SELECT nodeid,masterjustping FROM fnnode WHERE masterid=\"".$row['nodeid']."\"";
456: 		//echo $chq;
457: 		
458: 		$chr=$NATS->DB->Query($chq);
459: 		$spawnlist=array();
460: 		
461: 		while ($child=$NATS->DB->Fetch_Array($chr))
462: 			{
463: 			if (($child['masterjustping']==1)&&($pingpassed)) $spawnlist[]=$child['nodeid'];
464: 			else if ($alertlevel==0) $spawnlist[]=$child['nodeid'];
465: 			// logic: if the child requires a ping and ping has passed ok then spawn it
466: 			// otherwise (pass on any alert) if everything has passed spawn it
467: 			}
468: 		$NATS->DB->Free($chr);
469: 		
470: 		if (count($spawnlist)>0)
471: 			{
472: 			$cmd="php ./test-threaded.php";
473: 			foreach($spawnlist as $child)
474: 				$cmd.=" ".$child;
475: 			$cmd.=" > /tmp/ns.master.".$row['nodeid']." &";
476: 			db("Children Spawning: ".$cmd);
477: 			exec($cmd);
478: 			}
479: 		
480: 		
481: 		// End of the node... carry forward highest level
482: 
483: 		db(" ");
484: 		
485: 		if ($alertlevel>$highalertlevel) $highalertlevel=$alertlevel;
486: 		$talertc+=$alertc;
487: 		
488: 		}
489: 
490: 
491: 
492: 	db("Finished Tests... Finishing Off");
493: 	db("Summary: Tester ".$trid." Highest Level ".$highalertlevel.", Alerts ".$talertc);
494: 	if ($highalertlevel>-1)
495: 		{
496: 		$uq="UPDATE fntestrun SET finishx=".time().",routput=\"".ss($dbt)."\" WHERE trid=".$trid;
497: 		$NATS->DB->Query($uq);
498: 		}
499: 	else
500: 		{
501: 		$uq="DELETE FROM fntestrun WHERE trid=".$trid;
502: 		$NATS->DB->Query($uq);
503: 		}
504: 
505: 
506: 	$NATS->Event("Tester ".$trid." Highest Level ".$highalertlevel.", Alerts ".$talertc,7,"Tester","Stat");
507: 	$NATS->Event("Tester ".$trid." Finished",5,"Tester","Stop");
508: 
509: 	// in here for now...
510: 	$NATS->ActionFlush();
511: 
512: } // end of suspend check
513: else
514: {
515: 	// Suspended
516: 	db("NATS Testing Suspended");
517: }
518: 
519: $NATS->Stop();
520: db("NATS Stopped... Finished");
521: ?>
522: 
523: