From 51fe68c2ee0527e73db0b8d11ce80f1e3e478a7a Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 22 Mar 2023 16:00:21 -0500 Subject: [PATCH] #9047 - Documentation and some more test fixes --- docs/development/models.md | 1 + docs/development/search.md | 2 +- docs/features/circuits.md | 6 ++-- docs/features/contacts.md | 1 + docs/getting-started/planning.md | 2 +- docs/models/circuits/circuit.md | 4 +-- docs/models/circuits/provider.md | 4 --- docs/models/circuits/provideraccount.md | 17 ++++++++++ netbox/circuits/models/circuits.py | 6 ++-- netbox/circuits/models/providers.py | 6 ++-- netbox/circuits/tests/test_filtersets.py | 17 +++++++--- netbox/dcim/tests/test_cablepaths.py | 7 ++-- netbox/dcim/tests/test_models.py | 5 +-- netbox/netbox/tests/test_staging.py | 41 ++++++++++++++++-------- 14 files changed, 80 insertions(+), 39 deletions(-) create mode 100644 docs/models/circuits/provideraccount.md diff --git a/docs/development/models.md b/docs/development/models.md index af11617c8..b507fbc5b 100644 --- a/docs/development/models.md +++ b/docs/development/models.md @@ -30,6 +30,7 @@ The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/ * [circuits.Circuit](../models/circuits/circuit.md) * [circuits.Provider](../models/circuits/provider.md) +* [circuits.ProviderAccount](../models/circuits/provideracount.md) * [circuits.ProviderNetwork](../models/circuits/providernetwork.md) * [dcim.Cable](../models/dcim/cable.md) * [dcim.Device](../models/dcim/device.md) diff --git a/docs/development/search.md b/docs/development/search.md index 02bcaa898..6ccffa7af 100644 --- a/docs/development/search.md +++ b/docs/development/search.md @@ -29,7 +29,7 @@ A SearchIndex subclass defines both its model and a list of two-tuples specifyin | 60 | Unique serialized attribute (per related object) | Device.serial | | 100 | Primary human identifier | Device.name, Circuit.cid, Cable.label | | 110 | Slug | Site.slug | -| 200 | Secondary identifier | Provider.account, DeviceType.part_number | +| 200 | Secondary identifier | ProviderAccount.account, DeviceType.part_number | | 300 | Highly unique descriptive attribute | CircuitTermination.xconnect_id, IPAddress.dns_name | | 500 | Description | Site.description | | 1000 | Custom field default | - | diff --git a/docs/features/circuits.md b/docs/features/circuits.md index 7739efb4c..f5bbe95f4 100644 --- a/docs/features/circuits.md +++ b/docs/features/circuits.md @@ -5,13 +5,15 @@ NetBox is ideal for managing your network's transit and peering providers and ci ```mermaid flowchart TD ASN --> Provider - Provider --> ProviderNetwork & Circuit + Provider --> ProviderAccount --> Circuit + Provider --> ProviderNetwork CircuitType --> Circuit click ASN "../../models/circuits/asn/" click Circuit "../../models/circuits/circuit/" click CircuitType "../../models/circuits/circuittype/" click Provider "../../models/circuits/provider/" +click ProviderAccount "../../models/circuits/provideraccount/" click ProviderNetwork "../../models/circuits/providernetwork/" ``` @@ -25,7 +27,7 @@ Sometimes you'll need to model provider networks into which you don't have full A circuit is a physical connection between two points, which is installed and maintained by an external provider. For example, an Internet connection delivered as a fiber optic cable would be modeled as a circuit in NetBox. -Each circuit is associated with a provider and assigned a circuit ID, which must be unique to that provider. A circuit is also assigned a user-defined type, operational status, and various other operating characteristics. +Each circuit is associated with a provider account and assigned a circuit ID, which must be unique to that provider. A circuit is also assigned a user-defined type, operational status, and various other operating characteristics. Each circuit may have up to two terminations (A and Z) defined. Each termination can be associated with a particular site or provider network. In the case of the former, a cable can be connected between the circuit termination and a device component to map its physical connectivity. diff --git a/docs/features/contacts.md b/docs/features/contacts.md index 40e8dd12c..ed182134c 100644 --- a/docs/features/contacts.md +++ b/docs/features/contacts.md @@ -31,6 +31,7 @@ The following models support the assignment of contacts: * circuits.Circuit * circuits.Provider +* circuits.ProviderAccount * dcim.Device * dcim.Location * dcim.Manufacturer diff --git a/docs/getting-started/planning.md b/docs/getting-started/planning.md index 5dbe6e54e..64cc04ec9 100644 --- a/docs/getting-started/planning.md +++ b/docs/getting-started/planning.md @@ -56,7 +56,7 @@ Below is the (rough) recommended order in which NetBox objects should be created 4. Manufacturers, device types, and module types 5. Platforms and device roles 6. Devices and modules -7. Providers and provider networks +7. Providers, provider accounts and provider networks 8. Circuit types and circuits 9. Wireless LAN groups and wireless LANs 10. Route targets and VRFs diff --git a/docs/models/circuits/circuit.md b/docs/models/circuits/circuit.md index 50637ab4e..83c3264f1 100644 --- a/docs/models/circuits/circuit.md +++ b/docs/models/circuits/circuit.md @@ -4,9 +4,9 @@ A circuit represents a physical point-to-point data connection, typically used t ## Fields -### Provider +### Provider Account -The [provider](./provider.md) to which this circuit belongs. +The [provider account](./provideraccount.md) to which this circuit belongs. ### Circuit ID diff --git a/docs/models/circuits/provider.md b/docs/models/circuits/provider.md index a4835199e..e8610d230 100644 --- a/docs/models/circuits/provider.md +++ b/docs/models/circuits/provider.md @@ -23,10 +23,6 @@ The AS number assigned to this provider. The [AS numbers](../ipam/asn.md) assigned to this provider (optional). -### Account Number - -The administrative account identifier tied to this provider for your organization. - ### Portal URL The URL for the provider's customer service portal. diff --git a/docs/models/circuits/provideraccount.md b/docs/models/circuits/provideraccount.md new file mode 100644 index 000000000..e6836aaea --- /dev/null +++ b/docs/models/circuits/provideraccount.md @@ -0,0 +1,17 @@ +# Provider Accounts + +This model can be used to represent individual accounts associated with a provider. + +## Fields + +### Provider + +The [provider](./provider.md) the account belongs to. + +### Name + +A human-friendly name, unique to the provider. + +### Account Number + +The administrative account identifier tied to this provider for your organization. \ No newline at end of file diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py index 3342697df..372680538 100644 --- a/netbox/circuits/models/circuits.py +++ b/netbox/circuits/models/circuits.py @@ -30,9 +30,9 @@ class CircuitType(OrganizationalModel): class Circuit(PrimaryModel): """ - A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple - circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured - in Kbps. + A communications circuit connects two points. Each Circuit belongs to a Provider Account; ProviderAccounts may have + multiple circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are + measured in Kbps. """ cid = models.CharField( max_length=100, diff --git a/netbox/circuits/models/providers.py b/netbox/circuits/models/providers.py index 6cbe6534b..5e8c21526 100644 --- a/netbox/circuits/models/providers.py +++ b/netbox/circuits/models/providers.py @@ -14,8 +14,8 @@ __all__ = ( class Provider(PrimaryModel): """ - Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model - stores information pertinent to the user's relationship with the Provider. + This is usually a telecommunications company or similar organization. This model stores information pertinent to + the user's relationship with the Provider. """ name = models.CharField( max_length=100, @@ -50,7 +50,7 @@ class Provider(PrimaryModel): class ProviderAccount(PrimaryModel): """ - This represents a provider account + This is a discrete account within a provider. Each Circuit belongs to a Provider Account. """ account = models.CharField( max_length=30, diff --git a/netbox/circuits/tests/test_filtersets.py b/netbox/circuits/tests/test_filtersets.py index ca2511112..0cb898a98 100644 --- a/netbox/circuits/tests/test_filtersets.py +++ b/netbox/circuits/tests/test_filtersets.py @@ -36,6 +36,15 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests): providers[1].asns.set([asns[1]]) providers[2].asns.set([asns[2]]) + provider_accounts = ( + ProviderAccount(name='Account A', account='AAAA', provider=providers[0]), + ProviderAccount(name='Account B', account='BBBB', provider=providers[1]), + ProviderAccount(name='Account C', account='CCCC', provider=providers[2]), + ProviderAccount(name='Account D', account='DDDD', provider=providers[3]), + ProviderAccount(name='Account E', account='EEEE', provider=providers[4]), + ) + ProviderAccount.objects.bulk_create(provider_accounts) + regions = ( Region(name='Test Region 1', slug='test-region-1'), Region(name='Test Region 2', slug='test-region-2'), @@ -64,8 +73,8 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests): CircuitType.objects.bulk_create(circuit_types) circuits = ( - Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'), - Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 1'), + Circuit(provider_account=provider_accounts[0], type=circuit_types[0], cid='Test Circuit 1'), + Circuit(provider_account=provider_accounts[1], type=circuit_types[1], cid='Test Circuit 1'), ) Circuit.objects.bulk_create(circuits) @@ -249,8 +258,8 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_provider_account(self): - provider = ProviderAccount.objects.first() - params = {'provider_id': [provider.pk]} + provider_account = ProviderAccount.objects.first() + params = {'provider_account_id': [provider_account.pk]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) def test_provider_network(self): diff --git a/netbox/dcim/tests/test_cablepaths.py b/netbox/dcim/tests/test_cablepaths.py index 3367a3efe..c7b435a81 100644 --- a/netbox/dcim/tests/test_cablepaths.py +++ b/netbox/dcim/tests/test_cablepaths.py @@ -30,8 +30,9 @@ class CablePathTestCase(TestCase): cls.powerpanel = PowerPanel.objects.create(site=cls.site, name='Power Panel') provider = Provider.objects.create(name='Provider', slug='provider') + provider_account = ProviderAccount.objects.create(name='Account', account='AAAA1111', provider=provider) circuit_type = CircuitType.objects.create(name='Circuit Type', slug='circuit-type') - cls.circuit = Circuit.objects.create(provider=provider, type=circuit_type, cid='Circuit 1') + cls.circuit = Circuit.objects.create(provider_account=provider_account, type=circuit_type, cid='Circuit 1') def assertPathExists(self, nodes, **kwargs): """ @@ -1308,7 +1309,7 @@ class CablePathTestCase(TestCase): [IF1] --C1-- [CT1] [CT2] --> [PN1] """ interface1 = Interface.objects.create(device=self.device, name='Interface 1') - providernetwork = ProviderNetwork.objects.create(name='Provider Network 1', provider=self.circuit.provider) + providernetwork = ProviderNetwork.objects.create(name='Provider Network 1', provider=self.circuit.provider_account.provider) circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A') circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, provider_network=providernetwork, term_side='Z') @@ -1436,7 +1437,7 @@ class CablePathTestCase(TestCase): """ interface1 = Interface.objects.create(device=self.device, name='Interface 1') interface2 = Interface.objects.create(device=self.device, name='Interface 2') - circuit2 = Circuit.objects.create(provider=self.circuit.provider, type=self.circuit.type, cid='Circuit 2') + circuit2 = Circuit.objects.create(provider_account=self.circuit.provider_account, type=self.circuit.type, cid='Circuit 2') circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='A') circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, site=self.site, term_side='Z') circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, site=self.site, term_side='A') diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index e9a577648..d911b1404 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -504,10 +504,11 @@ class CableTestCase(TestCase): device=patch_pannel, name='FP4', type='8p8c', rear_port=rear_port4, rear_port_position=1 ) provider = Provider.objects.create(name='Provider 1', slug='provider-1') + provider_account = ProviderAccount.objects.create(name='Provider Account 1', account='A1', provider=provider) provider_network = ProviderNetwork.objects.create(name='Provider Network 1', provider=provider) circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1') - circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1') - circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2') + circuit1 = Circuit.objects.create(provider_account=provider_account, type=circuittype, cid='1') + circuit2 = Circuit.objects.create(provider_account=provider_account, type=circuittype, cid='2') circuittermination1 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='A') circuittermination2 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='Z') circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, provider_network=provider_network, term_side='A') diff --git a/netbox/netbox/tests/test_staging.py b/netbox/netbox/tests/test_staging.py index ed3a69f10..f6b6d62ae 100644 --- a/netbox/netbox/tests/test_staging.py +++ b/netbox/netbox/tests/test_staging.py @@ -1,6 +1,6 @@ from django.test import TransactionTestCase -from circuits.models import Provider, Circuit, CircuitType +from circuits.models import Provider, Circuit, CircuitType, ProviderAccount from extras.choices import ChangeActionChoices from extras.models import Branch, StagedChange, Tag from ipam.models import ASN, RIR @@ -28,18 +28,25 @@ class StagingTestCase(TransactionTestCase): ) Provider.objects.bulk_create(providers) + provider_accounts = ( + ProviderAccount(name='Account A', provider=providers[0], account='AAAA'), + ProviderAccount(name='Account B', provider=providers[1], account='BBBB'), + ProviderAccount(name='Account C', provider=providers[2], account='CCCC'), + ) + ProviderAccount.objects.bulk_create(provider_accounts) + circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1') Circuit.objects.bulk_create(( - Circuit(provider=providers[0], cid='Circuit A1', type=circuit_type), - Circuit(provider=providers[0], cid='Circuit A2', type=circuit_type), - Circuit(provider=providers[0], cid='Circuit A3', type=circuit_type), - Circuit(provider=providers[1], cid='Circuit B1', type=circuit_type), - Circuit(provider=providers[1], cid='Circuit B2', type=circuit_type), - Circuit(provider=providers[1], cid='Circuit B3', type=circuit_type), - Circuit(provider=providers[2], cid='Circuit C1', type=circuit_type), - Circuit(provider=providers[2], cid='Circuit C2', type=circuit_type), - Circuit(provider=providers[2], cid='Circuit C3', type=circuit_type), + Circuit(provider_account=provider_accounts[0], cid='Circuit A1', type=circuit_type), + Circuit(provider_account=provider_accounts[0], cid='Circuit A2', type=circuit_type), + Circuit(provider_account=provider_accounts[0], cid='Circuit A3', type=circuit_type), + Circuit(provider_account=provider_accounts[1], cid='Circuit B1', type=circuit_type), + Circuit(provider_account=provider_accounts[1], cid='Circuit B2', type=circuit_type), + Circuit(provider_account=provider_accounts[1], cid='Circuit B3', type=circuit_type), + Circuit(provider_account=provider_accounts[2], cid='Circuit C1', type=circuit_type), + Circuit(provider_account=provider_accounts[2], cid='Circuit C2', type=circuit_type), + Circuit(provider_account=provider_accounts[2], cid='Circuit C3', type=circuit_type), )) def test_object_creation(self): @@ -50,7 +57,8 @@ class StagingTestCase(TransactionTestCase): with checkout(branch): provider = Provider.objects.create(name='Provider D', slug='provider-d') provider.asns.set(asns) - circuit = Circuit.objects.create(provider=provider, cid='Circuit D1', type=CircuitType.objects.first()) + provider_account = ProviderAccount.objects.create(name='Account D', provider=provider, account='DDDD') + circuit = Circuit.objects.create(provider_account=provider_account, cid='Circuit D1', type=CircuitType.objects.first()) circuit.tags.set(tags) # Sanity-checking @@ -62,7 +70,7 @@ class StagingTestCase(TransactionTestCase): # Verify that changes have been rolled back after exiting the context self.assertEqual(Provider.objects.count(), 3) self.assertEqual(Circuit.objects.count(), 9) - self.assertEqual(StagedChange.objects.count(), 5) + self.assertEqual(StagedChange.objects.count(), 6) # Verify that changes are replayed upon entering the context with checkout(branch): @@ -145,26 +153,31 @@ class StagingTestCase(TransactionTestCase): with checkout(branch): provider = Provider.objects.get(name='Provider A') - provider.circuits.all().delete() + Circuit.objects.filter(provider_account__provider=provider).delete() + provider.accounts.all().delete() provider.delete() # Sanity-checking self.assertEqual(Provider.objects.count(), 2) + self.assertEqual(ProviderAccount.objects.count(), 2) self.assertEqual(Circuit.objects.count(), 6) # Verify that changes have been rolled back after exiting the context self.assertEqual(Provider.objects.count(), 3) + self.assertEqual(ProviderAccount.objects.count(), 3) self.assertEqual(Circuit.objects.count(), 9) - self.assertEqual(StagedChange.objects.count(), 4) + self.assertEqual(StagedChange.objects.count(), 5) # Verify that changes are replayed upon entering the context with checkout(branch): self.assertEqual(Provider.objects.count(), 2) + self.assertEqual(ProviderAccount.objects.count(), 2) self.assertEqual(Circuit.objects.count(), 6) # Verify that changes are applied and deleted upon branch merge branch.merge() self.assertEqual(Provider.objects.count(), 2) + self.assertEqual(ProviderAccount.objects.count(), 2) self.assertEqual(Circuit.objects.count(), 6) self.assertEqual(StagedChange.objects.count(), 0)