Skip to content

Commit 69c09ff

Browse files
committed
[IM] Allow an infrastructure to be excluded from IX-F Export - fixes #788
1 parent c983e24 commit 69c09ff

File tree

6 files changed

+103
-37
lines changed

6 files changed

+103
-37
lines changed

app/Http/Controllers/InfrastructureController.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ public function feInit(): void
102102
'title' => 'Country',
103103
'type' => self::$FE_COL_TYPES[ 'COUNTRY' ]
104104
],
105+
'exclude_from_ixf_export' => [
106+
'title' => 'Exclude from IX-F Export',
107+
'type' => self::$FE_COL_TYPES[ 'YES_NO' ]
108+
],
105109
'notes' => [
106110
'title' => 'Notes',
107111
'type' => self::$FE_COL_TYPES[ 'PARSDOWN' ]
@@ -171,7 +175,9 @@ protected function editPrepareForm( int $id ): array
171175
'shortname' => request()->old( 'shortname', $this->object->shortname ),
172176
'isPrimary' => request()->old( 'isPrimary', $this->object->isPrimary ),
173177
'country' => request()->old( 'country', in_array( $this->object->country, array_values( Countries::getListForSelect( 'iso_3166_2' ) ), false ) ? $this->object->country : null ),
174-
'notes' => request()->old( 'notes', $this->object->notes ),
178+
'exclude_from_ixf_export'
179+
=> request()->old( 'exclude_from_ixf_export', $this->object->exclude_from_ixf_export ),
180+
'notes' => request()->old( 'notes', $this->object->notes ),
175181
]);
176182

177183
return [

app/Models/Infrastructure.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
* @property int $ixp_id
6868
* @method static Builder|Infrastructure whereIxpId($value)
6969
* @property-read Collection<int, \IXP\Models\Vlan> $vlans
70+
* @property int $exclude_from_ixf_export
71+
* @method static Builder<static>|Infrastructure whereExcludeFromIxfExport($value)
7072
* @mixin Eloquent
7173
*/
7274
class Infrastructure extends Model
@@ -90,6 +92,7 @@ class Infrastructure extends Model
9092
'name',
9193
'shortname',
9294
'isPrimary',
95+
'exclude_from_ixf_export',
9396
'peeringdb_ix_id',
9497
'ixf_ix_id',
9598
'country',

app/Utils/Export/JsonSchema.php

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class JsonSchema
7171
self::EUROIX_JSON_VERSION_1_0,
7272
];
7373

74+
// the data to export
75+
private array $output = [];
76+
7477
/**
7578
* Get the JSON schema (for a given version or for the latest version)
7679
*
@@ -90,26 +93,26 @@ public function get( ?string $version = null, bool $asArray = false, bool $detai
9093
}
9194

9295
/** @psalm-suppress UndefinedConstant */
93-
$output = [
96+
$this->output = [
9497
'version' => $version,
9598
'generator' => 'IXP Manager v' . APPLICATION_VERSION,
9699
];
97100

98101
// normalise times to UTC for exports
99102
date_default_timezone_set('UTC');
100-
$output['timestamp'] = now()->toIso8601ZuluString();
103+
$this->output['timestamp'] = now()->toIso8601ZuluString();
101104

102-
$output['ixp_list'] = $this->getIXPInfo( $version );
103-
$output['member_list'] = $this->getMemberInfo( $version, $detailed, $tags );
105+
$infrasIncluded = $this->getIXPInfo( $version );
106+
$this->output['member_list'] = $this->getMemberInfo( $version, $detailed, $tags, $infrasIncluded );
104107

105108
// apply filters as some IXs don't want to export all details
106-
$output = $this->filter($output);
109+
$this->output = $this->filter($this->output);
107110

108111
if( $asArray ) {
109-
return $output;
112+
return $this->output;
110113
}
111114

112-
return json_encode( $output, JSON_PRETTY_PRINT )."\n";
115+
return json_encode( $this->output, JSON_PRETTY_PRINT )."\n";
113116
}
114117

115118
/**
@@ -132,18 +135,19 @@ public function sanitiseVersion( string $version ): string
132135
* Collate the IXP specific information for the JSON schema export
133136
*
134137
* @param string $version The version to collate the detail for
135-
*
136-
* @return (|(array|mixed|value-of<TArray>)[]|int|null|string)[][]
137-
*
138138
* @throws
139-
*
140-
* @psalm-return list{0?: array{shortname: null|string, name: , country: |string, url: , peeringdb_id?: int, ixf_id?: int, ixp_id: int, support_email: , support_phone: , support_contact_hours: , emergency_email: , emergency_phone: , emergency_contact_hours: , billing_contact_hours: , billing_email: , billing_phone: , peering_policy_list: non-empty-list<value-of<never>>, vlan: array<int<0, max>, array<string, mixed>>, switch: array},...}
141139
*/
142140
private function getIXPInfo( string $version ): array
143141
{
144142
$ixpinfo = [];
143+
$infrasIncluded = [];
145144

146145
foreach( Infrastructure::with( ['switchers.cabinet.location'] )->get() as $infra ) {
146+
147+
if( $infra->exclude_from_ixf_export ) {
148+
continue;
149+
}
150+
147151
$i = [];
148152
$i['shortname'] = $infra->name;
149153
$i['name'] = config('identity.legalname');
@@ -182,6 +186,8 @@ private function getIXPInfo( string $version ): array
182186
}
183187
}
184188

189+
$infrasIncluded[] = $infra->id;
190+
185191
$i['ixp_id'] = $infra->id; // referenced in member's connections section
186192

187193
$i['support_email'] = config('identity.support_email');
@@ -237,8 +243,10 @@ private function getIXPInfo( string $version ): array
237243

238244
$ixpinfo[] = $i;
239245
}
240-
241-
return $ixpinfo;
246+
247+
$this->output['ixp_list'] = $ixpinfo;
248+
249+
return $infrasIncluded;
242250
}
243251

244252
/**
@@ -288,16 +296,8 @@ private function getSwitchInfo( string $version, Infrastructure $infra ): array
288296

289297
/**
290298
* Collate the IXP's member information for the JSON schema export
291-
*
292-
* @param string $version The version to collate the detail for
293-
* @param bool $detailed
294-
* @param bool $tags
295-
*
296-
* @return ((((((stdClass|string)[]|bool|int|mixed|string)[]|int|null)[][]|mixed|string)[]|bool|int|null|string)[]|int|null|string)[][]
297-
*
298-
* @psalm-return array<int<0, max>, array{asnum: int|null, member_since: string, url: null|string, name: null|string, peering_policy: null|string, member_type: string, contact_email?: list{null|string}, contact_phone?: list{null|string}, peering_policy_url?: null|string, contact_hours?: string, ixp_manager?: array{tags: array<string, string>, in_manrs: bool, is_reseller: bool, is_resold: bool, resold_via_asn?: int|null}, connection_list: list<array{if_list: list{0?: array{if_phys_speed?: int|null, if_speed: int, switch_id: int|null}, ...<array{if_phys_speed?: int|null, if_speed: int, switch_id: int|null}>}, ixp_id: mixed, state: 'active', vlan_list: list{non-empty-array<non-empty-literal-string, array{address: mixed, as_macro?: string, mac_addresses?: list{0?: string, ...<string>}, max_prefix?: int, routeserver: bool, service_type?: list{'ixroutecollector'|'ixrouteserver', ...<'ixroutecollector'|'ixrouteserver'>}, services?: list{stdClass, ...<stdClass>}}|int|null>, ...<non-empty-array<non-empty-literal-string, array{address: mixed, as_macro?: string, mac_addresses?: list{0?: string, ...<string>}, max_prefix?: int, routeserver: bool, service_type?: list{'ixroutecollector'|'ixrouteserver', ...<'ixroutecollector'|'ixrouteserver'>}, services?: list{stdClass, ...<stdClass>}}|int|null>>}}>}>
299299
*/
300-
private function getMemberInfo( string $version, bool $detailed, bool $tags ): array
300+
private function getMemberInfo( string $version, bool $detailed, bool $tags, array $infrasIncluded ): array
301301
{
302302
$memberinfo = [];
303303

@@ -374,6 +374,9 @@ private function getMemberInfo( string $version, bool $detailed, bool $tags ): a
374374
}
375375
}
376376
}
377+
378+
// ensure we have at least one interface on an included infrastructure
379+
$atLeastOneInfraIsConnected = false;
377380

378381
foreach( $c->virtualInterfaces as $vi ) {
379382
$iflist = [];
@@ -386,6 +389,15 @@ private function getMemberInfo( string $version, bool $detailed, bool $tags ): a
386389
continue;
387390
}
388391

392+
// is this an infrastructure we are including?
393+
try {
394+
if( !in_array( $pi->switchPort->switcher->infrastructure, $infrasIncluded ) ) {
395+
continue;
396+
}
397+
} catch( \Exception $e ) {
398+
dd($pi);
399+
}
400+
389401
$atLeastOnePiIsPeering = true;
390402

391403
if( $pi->statusConnected() ) {
@@ -407,7 +419,9 @@ private function getMemberInfo( string $version, bool $detailed, bool $tags ): a
407419
if( !$atLeastOnePiIsPeering || !$atLeastOnePiIsConnected ) {
408420
continue;
409421
}
410-
422+
423+
$atLeastOneInfraIsConnected = true;
424+
411425
$vlanentries = [];
412426
foreach( $vi->vlanInterfaces as $vli ) {
413427
if( $vli->vlan->private || !$vli->vlan->export_to_ixf ) {
@@ -507,7 +521,7 @@ private function getMemberInfo( string $version, bool $detailed, bool $tags ): a
507521

508522
$connlist[] = $conn;
509523
}
510-
524+
511525
$memberinfo[ $cnt ] = [
512526
'asnum' => $c->autsys,
513527
'member_since' => \Carbon\Carbon::parse($c->datejoin)->format( 'Y-m-d' ).'T00:00:00Z',
@@ -546,10 +560,11 @@ private function getMemberInfo( string $version, bool $detailed, bool $tags ): a
546560
$memberinfo[ $cnt ][ 'ixp_manager' ][ 'resold_via_asn' ] = $c->resellerObject->autsys;
547561
}
548562
}
549-
550-
$memberinfo[ $cnt ][ 'connection_list' ] = $connlist;
551-
552-
$cnt++;
563+
564+
if( $atLeastOneInfraIsConnected ) {
565+
$memberinfo[ $cnt ][ 'connection_list' ] = $connlist;
566+
$cnt++;
567+
}
553568
}
554569

555570
return $memberinfo;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration {
8+
public function up(): void
9+
{
10+
Schema::table( 'infrastructure', function( Blueprint $table ) {
11+
$table->boolean( 'exclude_from_ixf_export' )->default( false )->after( 'ixf_ix_id' );
12+
} );
13+
}
14+
15+
public function down(): void
16+
{
17+
Schema::table( 'infrastructure', function( Blueprint $table ) {
18+
$table->dropColumn( 'exclude_from_ixf_export' );
19+
} );
20+
}
21+
};

resources/views/infrastructure/edit-form.foil.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@
5959
. "cached for two hours. Use 'artisan cache:clear' to reset it.");
6060
?>
6161

62+
<?= Former::checkbox( 'exclude_from_ixf_export' )
63+
->label( '&nbsp;' )
64+
->text( 'Exclude from IX-F Export' )
65+
->value( 1 )
66+
->inline()
67+
->blockHelp( "By default, all infrastructures (IXPs), their switches, connections, etc., are included in the IX-F export. "
68+
. "Check this box if you do not want to include this infrastructure in the export.<br><br>"
69+
. "If you are unsure, leave this unchecked. For more information, see the "
70+
. "<a href=\"https://docs.ixpmanager.org/latest/features/ixf-export/\" target=\"_blank\">IX-F Export documentation</a>."
71+
);
72+
?>
73+
74+
6275
<div class="form-group row">
6376
<div class="col-sm-8">
6477
<div class="card">

tests/Browser/InfrastructureControllerTest.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public function testAdd(): void
8989
->type( 'shortname', 'phpunit' )
9090
->select( 'country', 'IE' )
9191
->check( 'isPrimary' )
92+
->check( 'exclude_from_ixf_export' )
9293
->select( 'ixf_ix_id', '1' )
9394
->select( 'peeringdb_ix_id', '1' )
9495
->type( 'notes', 'I am a note' )
@@ -112,10 +113,11 @@ public function testAdd(): void
112113
$this->assertEquals( 'phpunit', $infra->shortname );
113114
$this->assertEquals( 'IE', $infra->country );
114115
$this->assertEquals( 'I am a note', $infra->notes );
115-
$this->assertEquals( true, $infra->isPrimary );
116116
$this->assertEquals( '1', $infra->ixf_ix_id );
117117
$this->assertEquals( '1', $infra->peeringdb_ix_id );
118-
118+
$this->assertTrue( $infra->isPrimary );
119+
$this->assertTrue( $infra->exclude_from_ixf_export );
120+
119121
// 3. browse to edit infrastructure object:
120122
$browser->click( '#e2f-list-edit-' . $infra->id )
121123
->waitForText( 'Edit Infrastructure' );
@@ -126,6 +128,7 @@ public function testAdd(): void
126128
->assertSelected( 'country', 'IE' )
127129
->assertInputValue( 'notes', 'I am a note' )
128130
->assertChecked( 'isPrimary' )
131+
->assertChecked( 'exclude_from_ixf_export' )
129132
->assertSelected( 'ixf_ix_id', '1' )
130133
->assertSelected( 'peeringdb_ix_id', '1' );
131134

@@ -135,6 +138,7 @@ public function testAdd(): void
135138
->select( 'peeringdb_ix_id', '2' )
136139
->select( 'country', 'FR' )
137140
->uncheck( 'isPrimary' )
141+
->uncheck( 'exclude_from_ixf_export' )
138142
->press( 'Save Changes' )
139143
->waitForLocation( '/infrastructure/list' )
140144
->assertSee( "Infrastructure updated" );
@@ -148,9 +152,10 @@ public function testAdd(): void
148152
$this->assertEquals( 'phpunit', $infra->shortname );
149153
$this->assertEquals( 'I am a note', $infra->notes );
150154
$this->assertEquals( 'FR', $infra->country );
151-
$this->assertEquals( false, $infra->isPrimary );
152155
$this->assertEquals( '2', $infra->ixf_ix_id );
153156
$this->assertEquals( '2', $infra->peeringdb_ix_id );
157+
$this->assertFalse( $infra->isPrimary );
158+
$this->assertFalse( $infra->exclude_from_ixf_export );
154159

155160

156161
// 7. edit again and assert that all checkboxes are unchecked and assert select values are as expected
@@ -161,6 +166,7 @@ public function testAdd(): void
161166
->assertInputValue( 'shortname', 'phpunit' )
162167
->assertInputValue( 'notes', 'I am a note' )
163168
->assertNotChecked( 'isPrimary' )
169+
->assertNotChecked( 'exclude_from_ixf_export' )
164170
->assertSelected( 'ixf_ix_id', '2' )
165171
->assertSelected( 'country', 'FR' )
166172
->assertSelected( 'peeringdb_ix_id', '2' );
@@ -178,23 +184,25 @@ public function testAdd(): void
178184
$this->assertEquals( 'Infrastructure PHPUnit', $infra->name );
179185
$this->assertEquals( 'phpunit', $infra->shortname );
180186
$this->assertEquals( 'FR', $infra->country );
181-
$this->assertEquals( false, $infra->isPrimary );
182187
$this->assertEquals( '2', $infra->ixf_ix_id );
183188
$this->assertEquals( '2', $infra->peeringdb_ix_id );
184-
189+
$this->assertFalse( $infra->isPrimary );
190+
$this->assertFalse( $infra->exclude_from_ixf_export );
185191

186192
// 9. edit again and check all checkboxes and submit
187193
$browser->visit( '/infrastructure/edit/' . $infra->id )
188194
->waitForText( 'Edit Infrastructure' )
189195
->check( 'isPrimary' )
196+
->check( 'exclude_from_ixf_export' )
190197
->press( 'Save Changes' )
191198
->waitForLocation( '/infrastructure/list' );
192199

193200

194201
// 10. verify checkbox bool elements in database are all true
195202
$infra->refresh();
196-
197-
$this->assertEquals( true, $infra->isPrimary );
203+
204+
$this->assertTrue( $infra->isPrimary );
205+
$this->assertTrue( $infra->exclude_from_ixf_export );
198206

199207
// 11. delete the router in the UI and verify via success message text and location
200208
$browser->visit( '/infrastructure/list/' )

0 commit comments

Comments
 (0)