#9047 - Documentation and some more test fixes

This commit is contained in:
Daniel Sheppard 2023-03-22 16:00:21 -05:00
parent 1a6bd726eb
commit 51fe68c2ee
14 changed files with 80 additions and 39 deletions

View File

@ -30,6 +30,7 @@ The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/
* [circuits.Circuit](../models/circuits/circuit.md) * [circuits.Circuit](../models/circuits/circuit.md)
* [circuits.Provider](../models/circuits/provider.md) * [circuits.Provider](../models/circuits/provider.md)
* [circuits.ProviderAccount](../models/circuits/provideracount.md)
* [circuits.ProviderNetwork](../models/circuits/providernetwork.md) * [circuits.ProviderNetwork](../models/circuits/providernetwork.md)
* [dcim.Cable](../models/dcim/cable.md) * [dcim.Cable](../models/dcim/cable.md)
* [dcim.Device](../models/dcim/device.md) * [dcim.Device](../models/dcim/device.md)

View File

@ -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 | | 60 | Unique serialized attribute (per related object) | Device.serial |
| 100 | Primary human identifier | Device.name, Circuit.cid, Cable.label | | 100 | Primary human identifier | Device.name, Circuit.cid, Cable.label |
| 110 | Slug | Site.slug | | 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 | | 300 | Highly unique descriptive attribute | CircuitTermination.xconnect_id, IPAddress.dns_name |
| 500 | Description | Site.description | | 500 | Description | Site.description |
| 1000 | Custom field default | - | | 1000 | Custom field default | - |

View File

@ -5,13 +5,15 @@ NetBox is ideal for managing your network's transit and peering providers and ci
```mermaid ```mermaid
flowchart TD flowchart TD
ASN --> Provider ASN --> Provider
Provider --> ProviderNetwork & Circuit Provider --> ProviderAccount --> Circuit
Provider --> ProviderNetwork
CircuitType --> Circuit CircuitType --> Circuit
click ASN "../../models/circuits/asn/" click ASN "../../models/circuits/asn/"
click Circuit "../../models/circuits/circuit/" click Circuit "../../models/circuits/circuit/"
click CircuitType "../../models/circuits/circuittype/" click CircuitType "../../models/circuits/circuittype/"
click Provider "../../models/circuits/provider/" click Provider "../../models/circuits/provider/"
click ProviderAccount "../../models/circuits/provideraccount/"
click ProviderNetwork "../../models/circuits/providernetwork/" 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. 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. 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.

View File

@ -31,6 +31,7 @@ The following models support the assignment of contacts:
* circuits.Circuit * circuits.Circuit
* circuits.Provider * circuits.Provider
* circuits.ProviderAccount
* dcim.Device * dcim.Device
* dcim.Location * dcim.Location
* dcim.Manufacturer * dcim.Manufacturer

View File

@ -56,7 +56,7 @@ Below is the (rough) recommended order in which NetBox objects should be created
4. Manufacturers, device types, and module types 4. Manufacturers, device types, and module types
5. Platforms and device roles 5. Platforms and device roles
6. Devices and modules 6. Devices and modules
7. Providers and provider networks 7. Providers, provider accounts and provider networks
8. Circuit types and circuits 8. Circuit types and circuits
9. Wireless LAN groups and wireless LANs 9. Wireless LAN groups and wireless LANs
10. Route targets and VRFs 10. Route targets and VRFs

View File

@ -4,9 +4,9 @@ A circuit represents a physical point-to-point data connection, typically used t
## Fields ## 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 ### Circuit ID

View File

@ -23,10 +23,6 @@ The AS number assigned to this provider.
The [AS numbers](../ipam/asn.md) assigned to this provider (optional). 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 ### Portal URL
The URL for the provider's customer service portal. The URL for the provider's customer service portal.

View File

@ -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.

View File

@ -30,9 +30,9 @@ class CircuitType(OrganizationalModel):
class Circuit(PrimaryModel): class Circuit(PrimaryModel):
""" """
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple A communications circuit connects two points. Each Circuit belongs to a Provider Account; ProviderAccounts may have
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured multiple circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are
in Kbps. measured in Kbps.
""" """
cid = models.CharField( cid = models.CharField(
max_length=100, max_length=100,

View File

@ -14,8 +14,8 @@ __all__ = (
class Provider(PrimaryModel): class Provider(PrimaryModel):
""" """
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model This is usually a telecommunications company or similar organization. This model stores information pertinent to
stores information pertinent to the user's relationship with the Provider. the user's relationship with the Provider.
""" """
name = models.CharField( name = models.CharField(
max_length=100, max_length=100,
@ -50,7 +50,7 @@ class Provider(PrimaryModel):
class ProviderAccount(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( account = models.CharField(
max_length=30, max_length=30,

View File

@ -36,6 +36,15 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
providers[1].asns.set([asns[1]]) providers[1].asns.set([asns[1]])
providers[2].asns.set([asns[2]]) 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 = ( regions = (
Region(name='Test Region 1', slug='test-region-1'), Region(name='Test Region 1', slug='test-region-1'),
Region(name='Test Region 2', slug='test-region-2'), Region(name='Test Region 2', slug='test-region-2'),
@ -64,8 +73,8 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
CircuitType.objects.bulk_create(circuit_types) CircuitType.objects.bulk_create(circuit_types)
circuits = ( circuits = (
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'), Circuit(provider_account=provider_accounts[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[1], type=circuit_types[1], cid='Test Circuit 1'),
) )
Circuit.objects.bulk_create(circuits) Circuit.objects.bulk_create(circuits)
@ -249,8 +258,8 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_provider_account(self): def test_provider_account(self):
provider = ProviderAccount.objects.first() provider_account = ProviderAccount.objects.first()
params = {'provider_id': [provider.pk]} params = {'provider_account_id': [provider_account.pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_provider_network(self): def test_provider_network(self):

View File

@ -30,8 +30,9 @@ class CablePathTestCase(TestCase):
cls.powerpanel = PowerPanel.objects.create(site=cls.site, name='Power Panel') cls.powerpanel = PowerPanel.objects.create(site=cls.site, name='Power Panel')
provider = Provider.objects.create(name='Provider', slug='provider') 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') 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): def assertPathExists(self, nodes, **kwargs):
""" """
@ -1308,7 +1309,7 @@ class CablePathTestCase(TestCase):
[IF1] --C1-- [CT1] [CT2] --> [PN1] [IF1] --C1-- [CT1] [CT2] --> [PN1]
""" """
interface1 = Interface.objects.create(device=self.device, name='Interface 1') 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') 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') 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') interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2') 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') 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') 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') circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, site=self.site, term_side='A')

View File

@ -504,10 +504,11 @@ class CableTestCase(TestCase):
device=patch_pannel, name='FP4', type='8p8c', rear_port=rear_port4, rear_port_position=1 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 = 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) provider_network = ProviderNetwork.objects.create(name='Provider Network 1', provider=provider)
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1') circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1') circuit1 = Circuit.objects.create(provider_account=provider_account, type=circuittype, cid='1')
circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2') circuit2 = Circuit.objects.create(provider_account=provider_account, type=circuittype, cid='2')
circuittermination1 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='A') circuittermination1 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='A')
circuittermination2 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='Z') circuittermination2 = CircuitTermination.objects.create(circuit=circuit1, site=site, term_side='Z')
circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, provider_network=provider_network, term_side='A') circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, provider_network=provider_network, term_side='A')

View File

@ -1,6 +1,6 @@
from django.test import TransactionTestCase 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.choices import ChangeActionChoices
from extras.models import Branch, StagedChange, Tag from extras.models import Branch, StagedChange, Tag
from ipam.models import ASN, RIR from ipam.models import ASN, RIR
@ -28,18 +28,25 @@ class StagingTestCase(TransactionTestCase):
) )
Provider.objects.bulk_create(providers) 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_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
Circuit.objects.bulk_create(( Circuit.objects.bulk_create((
Circuit(provider=providers[0], cid='Circuit A1', type=circuit_type), Circuit(provider_account=provider_accounts[0], cid='Circuit A1', type=circuit_type),
Circuit(provider=providers[0], cid='Circuit A2', type=circuit_type), Circuit(provider_account=provider_accounts[0], cid='Circuit A2', type=circuit_type),
Circuit(provider=providers[0], cid='Circuit A3', type=circuit_type), Circuit(provider_account=provider_accounts[0], cid='Circuit A3', type=circuit_type),
Circuit(provider=providers[1], cid='Circuit B1', type=circuit_type), Circuit(provider_account=provider_accounts[1], cid='Circuit B1', type=circuit_type),
Circuit(provider=providers[1], cid='Circuit B2', type=circuit_type), Circuit(provider_account=provider_accounts[1], cid='Circuit B2', type=circuit_type),
Circuit(provider=providers[1], cid='Circuit B3', type=circuit_type), Circuit(provider_account=provider_accounts[1], cid='Circuit B3', type=circuit_type),
Circuit(provider=providers[2], cid='Circuit C1', type=circuit_type), Circuit(provider_account=provider_accounts[2], cid='Circuit C1', type=circuit_type),
Circuit(provider=providers[2], cid='Circuit C2', type=circuit_type), Circuit(provider_account=provider_accounts[2], cid='Circuit C2', type=circuit_type),
Circuit(provider=providers[2], cid='Circuit C3', type=circuit_type), Circuit(provider_account=provider_accounts[2], cid='Circuit C3', type=circuit_type),
)) ))
def test_object_creation(self): def test_object_creation(self):
@ -50,7 +57,8 @@ class StagingTestCase(TransactionTestCase):
with checkout(branch): with checkout(branch):
provider = Provider.objects.create(name='Provider D', slug='provider-d') provider = Provider.objects.create(name='Provider D', slug='provider-d')
provider.asns.set(asns) 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) circuit.tags.set(tags)
# Sanity-checking # Sanity-checking
@ -62,7 +70,7 @@ class StagingTestCase(TransactionTestCase):
# Verify that changes have been rolled back after exiting the context # Verify that changes have been rolled back after exiting the context
self.assertEqual(Provider.objects.count(), 3) self.assertEqual(Provider.objects.count(), 3)
self.assertEqual(Circuit.objects.count(), 9) 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 # Verify that changes are replayed upon entering the context
with checkout(branch): with checkout(branch):
@ -145,26 +153,31 @@ class StagingTestCase(TransactionTestCase):
with checkout(branch): with checkout(branch):
provider = Provider.objects.get(name='Provider A') 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() provider.delete()
# Sanity-checking # Sanity-checking
self.assertEqual(Provider.objects.count(), 2) self.assertEqual(Provider.objects.count(), 2)
self.assertEqual(ProviderAccount.objects.count(), 2)
self.assertEqual(Circuit.objects.count(), 6) self.assertEqual(Circuit.objects.count(), 6)
# Verify that changes have been rolled back after exiting the context # Verify that changes have been rolled back after exiting the context
self.assertEqual(Provider.objects.count(), 3) self.assertEqual(Provider.objects.count(), 3)
self.assertEqual(ProviderAccount.objects.count(), 3)
self.assertEqual(Circuit.objects.count(), 9) 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 # Verify that changes are replayed upon entering the context
with checkout(branch): with checkout(branch):
self.assertEqual(Provider.objects.count(), 2) self.assertEqual(Provider.objects.count(), 2)
self.assertEqual(ProviderAccount.objects.count(), 2)
self.assertEqual(Circuit.objects.count(), 6) self.assertEqual(Circuit.objects.count(), 6)
# Verify that changes are applied and deleted upon branch merge # Verify that changes are applied and deleted upon branch merge
branch.merge() branch.merge()
self.assertEqual(Provider.objects.count(), 2) self.assertEqual(Provider.objects.count(), 2)
self.assertEqual(ProviderAccount.objects.count(), 2)
self.assertEqual(Circuit.objects.count(), 6) self.assertEqual(Circuit.objects.count(), 6)
self.assertEqual(StagedChange.objects.count(), 0) self.assertEqual(StagedChange.objects.count(), 0)