diff --git a/app/Console/Commands/ZoneCheck.php b/app/Console/Commands/ZoneCheck.php new file mode 100644 index 0000000..78f75ea --- /dev/null +++ b/app/Console/Commands/ZoneCheck.php @@ -0,0 +1,100 @@ +argument('domain'))->singleOrFail(); + + foreach ($do->zones as $zo) { + if ($this->option('zone') && ($this->option('zone') != $zo->zone_id)) + continue; + + $this->warn('Zone: '.$zo->zone_id); + $this->info(sprintf('- Our address(es): %s',our_address($zo)->pluck('ftn4d')->join(','))); + + $this->table(['id','ftn','role','parent','region_id','host_id','hub_id','system','notes'],$zo->addresses()->FTNorder()->active()->with(['system'])->get()->transform(function($item) { + return [ + 'id'=>$item->id, + 'ftn'=>$item->ftn4d, + 'role'=>$item->role_name, + 'parent'=>$item->parent()?->ftn4d, + 'region_id'=>$item->region_id, + 'host_id'=>$item->host_id, + 'hub_id'=>$item->hub_id, + 'system'=>$item->system->name, + 'notes'=>$this->check($item), + ]; + })); + } + + return Command::SUCCESS; + } + + /** + * Check that an address is defined correctly + * + * @param Address $ao + * @return string + */ + private function check(Address $ao): string + { + // ZC address + if ($ao->role === Address::NODE_ZC) { + if (($ao->region_id === 0) && ($ao->host_id === 0) && ($ao->node_id === 0) && is_null($ao->hub_id) && ($ao->point_id === 0)) + return 'OK'; + + else + return 'INVALID ZC address'; + } + + // RC address + if ($ao->role === Address::NODE_RC) { + if ($ao->region_id && ($ao->region_id === $ao->host_id) && ($ao->node_id === 0) && is_null($ao->hub_id) && ($ao->point_id === 0)) + return 'OK'; + else + return 'INVALID RC address'; + } + + // NC address + if ($ao->role === Address::NODE_NC) { + if (($ao->node_id !== 0) && is_null($ao->hub_id) && ($ao->point_id === 0)) + return 'OK'; + else + return 'INVALID NC address'; + } + + // HUB address + if ($ao->role === Address::NODE_HC) { + if (($ao->node_id !== 0) && is_null($ao->hub_id) && ($ao->point_id === 0)) + return 'OK'; + else + return 'INVALID HUB address'; + } + + // POINT address + if ($ao->role === Address::NODE_POINT) { + if ($ao->point_id !== 0) + return 'OK'; + else + return 'INVALID POINT address'; + } + + if ($ao->region_id && ($ao->host_id === 0)) + return 'INVALID REGION NODE'; + + return ''; + } +} \ No newline at end of file diff --git a/app/Models/Address.php b/app/Models/Address.php index 33bb3ab..92ee5c3 100644 --- a/app/Models/Address.php +++ b/app/Models/Address.php @@ -150,7 +150,6 @@ class Address extends Model /** * Find children dependent on this record - * @todo If bosses are defined here, and points, then mail to a point goes to it's boss */ public function children() { @@ -194,9 +193,18 @@ class Address extends Model case self::NODE_PVT: case self::NODE_HOLD: case self::NODE_DOWN: - case self::NODE_POINT: case self::NODE_UNKNOWN: - // Nodes dont have children, but must return a relationship instance + // Identify our children. + $children = self::select('addresses.*') + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->host_id) + ->where('node_id',$this->node_id) + ->where('point_id','<>',0); + break; + + case self::NODE_POINT: + // Points dont have children, but must return a relationship instance return $this->hasOne(self::class,NULL,'void'); default: @@ -298,14 +306,17 @@ class Address extends Model * * @return Address|null * @throws \Exception - * @todo Dont include points in this */ public function parent(): ?Address { - // If we are have session password, then we dont have a parent + // If we have session password, then we are the parent if ($this->session('sespass')) return $this; + // If it is our address + if (our_address()->contains($this)) + return NULL; + switch ($this->role) { // ZCs dont have parents, but we may have a default case self::NODE_ZC: @@ -326,15 +337,15 @@ class Address extends Model // Hosts case self::NODE_NC: - // See if we have a RC + // See if we have an RC $parent = self::where('zone_id',$this->zone_id) ->where('region_id',$this->region_id) - ->where('host_id',0) + ->where('host_id',$this->region_id) ->where('node_id',0) ->single(); if (! $parent) { - // See if we have a RC + // See if we have an ZC $parent = self::where('zone_id',$this->zone_id) ->where('region_id',0) ->where('host_id',0) @@ -351,22 +362,27 @@ class Address extends Model case self::NODE_PVT: case self::NODE_HOLD: case self::NODE_DOWN: + case self::NODE_UNKNOWN: // If we are a child of a hub, then check our hub $parent = ($this->hub_id ? self::where('id',$this->hub_id) : self::where('zone_id',$this->zone_id) ->where('region_id',$this->region_id) ->where('host_id',$this->host_id) - ->where('node_id',0) - ->where('role','<',$this->role)) + ->where('role','<',self::NODE_HC)) + ->active() ->single(); - break; - case self::NODE_UNKNOWN: case self::NODE_POINT: - // @todo Points - if the boss is defined, we should return it. - return NULL; + $parent = self::where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->host_id) + ->where('node_id',$this->node_id) + ->where('point_id',0) + ->active() + ->single(); + break; default: throw new \Exception(sprintf('Unknown role: %s (%d)',serialize($this->role),$this->id)); diff --git a/database/seeders/TestNodeHierarchy.php b/database/seeders/TestNodeHierarchy.php index 6340fa1..94fad56 100644 --- a/database/seeders/TestNodeHierarchy.php +++ b/database/seeders/TestNodeHierarchy.php @@ -190,7 +190,7 @@ class TestNodeHierarchy extends Seeder 'validated'=>TRUE, 'region_id'=>$rid, 'host_id'=>$hostid, - 'node_id'=>0, + 'node_id'=>7, 'point_id'=>0, 'system_id'=>$so->id, 'role'=>Address::NODE_NC, diff --git a/tests/Feature/RoutingTest.php b/tests/Feature/RoutingTest.php index c1dd7fc..92a73d6 100644 --- a/tests/Feature/RoutingTest.php +++ b/tests/Feature/RoutingTest.php @@ -6,8 +6,48 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\TestCase; -use App\Models\{Address,Domain,Setup}; +use App\Models\{Address,Domain,Setup,System}; +/** + * Here we test mail routing. + * + * - COMMON to all routing nodes + * + We have RCs defined - everything for a node in the Region goes to an RC, unless RC's node defined locally, or nodes assigned to ZC + * ie: region_id is the same, find the NC/HUB/NODE address for the system_id that has been assigned 0/0 in the same region. + * nodes assigned to ZC have region_id = 0. Nodes with hub_id = NULL and host_id=0 are RC nodes. + * + We have NCs defined - everything under the Host goes to the NC, unless NC's nodes defined locally or nodes assigned to the RC + * ie: node_id defines the NCs responsible. Nodes with hub_id = NULL are an NC node. + * + We have HUBs defined - everything under the Hub goes to the HUB, unless HUB's nodes defined locally + * ie: hub_id defines who the hub is. + * + We have NODES defined - mail collected locally, including mail for it's point + * + * - Routing scenarios: + * - We are ZC for a domain + * + See COMMON + * + Everything else is collected locally + * + * - We are RC for a domain + * + See COMMON + * + Any nodes in our RC not defined with an NC is collected locally, including points + * + Everything else is sent to the ZC (our uplink) + * + * - We are NC for a domain + * + See COMMON + * + Any nodes in our NC not defined with a HUB is collected locally, including points + * + Everything else is sent to the RC or ZC (our uplink) + * + * - We are a Hub for a domain + * + See COMMON + * + Any nodes in our HUB is collected locally, including points + * + Everything else is sent to the NC or RC (our uplink) + * + * - We are a Node in a domain + * + See COMMON + * + Everything else is sent to the HUB or Host or RC (our uplink) + * + * @see Address::parent() + * @see Address::children() + */ class RoutingTest extends TestCase { use DatabaseTransactions; @@ -38,7 +78,7 @@ class RoutingTest extends TestCase private function session_nc(): void { // Add session info, and we have 51 children - $ao = Address::findFTN('100:10/0@a'); + $ao = Address::findFTN('100:10/7@a'); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); } @@ -93,8 +133,17 @@ class RoutingTest extends TestCase $this->assertEquals('101:0/0.0@a',$ao->parent()->ftn); } + // Get a list of active addresses in a Region + public function test_rc_session_children() + { + $this->session_rc(); + + $ao = Address::findFTN('100:1/0@a'); + $this->assertEquals(185,$ao->children()->count()); + } + // An RCs node still collects mail from the RC - public function test_zc_rc_node_rc() + public function test_rc_node_rc() { $this->session_rc(); @@ -105,58 +154,45 @@ class RoutingTest extends TestCase } // An NC collects mail for its children - public function test_zc_nc_node_rc() + public function test_rc_nc_node_rc() { - return $this->assertTrue(TRUE); $this->session_rc(); // A NCs parent should still be the RC - $ao = Address::findFTN('100:10/0@a'); + $ao = Address::findFTN('100:10/7@a'); $this->assertEquals($ao->role,Address::NODE_NC); - $this->assertEquals('100:1/0.0@a',$ao->parent()->ftn); // @todo fails, returning NULL? + $this->assertEquals('100:1/0.0@a',$ao->parent()->ftn); } // A Hub still collects mail from NC - public function test_zc_hub_node_nc() + public function test_rc_hub_node_nc() { - return $this->assertTrue(TRUE); $this->session_rc(); // A Hubs parent should still be the NC $ao = Address::findFTN('100:10/20.0@a'); $this->assertEquals($ao->role,Address::NODE_HC); - $this->assertEquals('100:1/0.0@a',$ao->parent()->ftn); // @todo fails, returning NULL? + $this->assertEquals('100:1/0.0@a',$ao->parent()->ftn); } // A Hub's node still collects mail from Hub - public function test_zc_hub_node_hub() + public function test_rc_hub_node_hub() { - return $this->assertTrue(TRUE); $this->session_rc(); // A Hubs node should still be the Hub $ao = Address::findFTN('100:10/22.0@a'); $this->assertEquals($ao->role,Address::NODE_ACTIVE); - $this->assertEquals('100:1/0.0@a',$ao->parent()->ftn); // @todo fails, returning NULL? - } - - // When we have an RC with session details, we route to all its children - public function test_rc_session_children() - { - $this->session_rc(); - - $ao = Address::findFTN('100:1/0@a'); - $this->assertCount(185,$ao->children); + $this->assertEquals('100:1/0.0@a',$ao->parent()->ftn); } // An RCs parent is us even if we have session details for another RC public function test_rc_parent() { - return $this->assertTrue(TRUE); $this->session_rc(); $ao = Address::findFTN('100:2/0@a'); - $this->assertEquals('100:0/0.0@a',$ao->parent()->ftn); // @todo fails, returning NULL? + $this->assertNull($ao->parent()?->ftn); } // If we also have session details for an NC, then there are less RC nodes @@ -164,7 +200,7 @@ class RoutingTest extends TestCase { $this->session_rc(); - $ao = Address::findFTN('100:10/0@a'); + $ao = Address::findFTN('100:10/7@a'); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); $ao = Address::findFTN('100:1/0@a'); @@ -185,6 +221,7 @@ class RoutingTest extends TestCase $ao = Address::findFTN('100:10/22@a'); $this->assertEquals('100:10/20.0@a',$ao->parent()->ftn); } + // If we also have session details for an Hub, then there are less RC nodes public function test_rc_hub_session_child() { @@ -193,4 +230,40 @@ class RoutingTest extends TestCase $ao = Address::findFTN('100:10/22@a'); $this->assertEquals('100:10/20.0@a',$ao->parent()->ftn); } + + // When we have an RC with session details, we route to all its children + public function test_nc_session_children() + { + $this->session_nc(); + + $ao = Address::findFTN('100:10/7@a'); + $this->assertCount(35,$ao->children); + } + + // A points parent is the node, if we have traffic for a point and we have session details for the node + public function test_point_session_node() + { + //$this->session_hub(); + + $so = new System; + $so->name = 'Point System'; + $so->sysop = 'Point Sysop'; + $so->location = 'Melbourne, AU'; + $so->active = TRUE; + $so->save(); + + // Create a child + $ao = Address::createFTN('100:10/21.2@a',$so); + $ao->region_id = 1; // @todo This should be worked out from the parent node (if exists), or another node on the same host + $ao->save(); + + $ao = Address::findFTN('100:10/21.0@a'); + $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); + + $ao = Address::findFTN('100:10/21.2@a'); + $this->assertEquals('100:10/21.0@a',$ao->parent()?->ftn); + + $ao = Address::findFTN('100:10/21@a'); + $this->assertCount(1,$ao->children); + } } \ No newline at end of file