Migrating to Nautobot from NetBox¶
Review the Release Notes¶
Be sure to carefully review all release notes that have been published. In particular, the Nautobot 1.0 release notes include an overview of key changes between NetBox 2.10 and Nautobot 1.0, while later release notes highlight incremental changes between Nautobot versions.
Install Nautobot¶
Install Nautobot as described in the documentation.
Configure Nautobot¶
Although Nautobot will run perfectly well with a default configuration (such as generated by nautobot-server init
, you may want to replicate aspects of your previous NetBox configuration to Nautobot. Refer to the configuration documentation for details on the available options.
Migrate Database Contents Using nautobot-netbox-importer
¶
Due to a number of significant infrastructural changes between the applications, you cannot simply point Nautobot at your existing NetBox PostgreSQL database and have it automatically load your data. Fortunately, Network to Code (NTC) and collaborators have developed a Nautobot plugin, nautobot-netbox-importer
, that can be used to import a NetBox database dump file into Nautobot. For full details, refer to the plugin's own documentation, but here is a brief overview:
- Export your NetBox database to a JSON file.
- Install the importer plugin.
- Enable the importer plugin.
- Run the plugin's import command to import the data.
- Connect to Nautobot and verify that your data has been successfully imported.
Migrate Files from NetBox to Nautobot¶
Uploaded media (device images, etc.) are stored on the filesystem rather than in the database and hence need to be migrated separately. The same is true for custom scripts and reports that you may wish to import.
Copy Uploaded Media¶
The exact command will depend on where your MEDIA_ROOT
is configured in NetBox as well as where it's configured in Nautobot, but in general it will be:
Copy Custom Scripts and Reports¶
Similarly, the exact commands depend on your SCRIPTS_ROOT
and REPORTS_ROOT
settings in NetBox and your JOBS_ROOT
in Nautobot, but in general they will be:
cp -pr $NETBOX_SCRIPTS_ROOT/* $NAUTOBOT_JOBS_ROOT/
cp -pr $NETBOX_REPORTS_ROOT/* $NAUTOBOT_JOBS_ROOT/
Update Scripts, Reports, and Plugins for Nautobot compatibility¶
Depending on the complexity of your scripts, reports, or plugins, and how tightly integrated with NetBox they were, it may be simple or complex to port them to be compatible with Nautobot, and we cannot possibly provide a generalized step-by-step guide that would cover all possibilities. One change that you will certainly have to make to even begin this process, however, is updating the Python module names for any modules that were being imported from NetBox:
circuits.* -> nautobot.circuits.*
dcim.* -> nautobot.dcim.*
extras.* -> nautobot.extras.*
ipam.* -> nautobot.ipam.*
netbox.* -> nautobot.core.*
tenancy.* -> nautobot.tenancy.*
utilities.* -> nautobot.utilities.*
virtualization.* -> nautobot.virtualization.*
Update Your other Integration Code¶
If you have developed any custom integrations or plugins you may need to update some of your calls. Please see the data model changes below for guidance.
Data Model Changes¶
The following backwards-incompatible changes have been made to the data model in Nautobot.
Status Fields¶
Tip
Status names are now lower-cased when setting the status
field on CSV imports. The slug
value is used for create/update of objects and for filtering in the API.
A new Status
model has been added to represent the status
field for many models. Each status has a human-readable name
field (e.g. Active
), and a slug
field (e.g. active
).
Display name¶
Several models such as device type and VLAN exposed a display_name
property, which has now been renamed to display
. In fact, there are several other instances, especially in the REST API, where the display_name
field was used and as such, all instances have been renamed to display
.
CSV Imports¶
When using CSV import to define a status
field on imported objects, such as when importing Devices or Prefixes, the Status.slug
field is used.
For example, the built-in Active status has a slug of active
, so the active
value would be used for import.
Default Choices¶
Because status
fields are now stored in the database, they cannot have a default value, just like other similar objects like Device Roles or Device Types. In cases where status
was not required to be set because it would use the default value, you must now provide a status
yourself.
Note
For consistency in the API, the slug
value of a status
is used when creating or updating an object.
Choices in Code¶
All *StatusChoices
enums used for populated status
field choices (such as nautobot.dcim.choices.DeviceStatusChoices
) are deprecated. Any code you have that is leveraging these will now result in an error when performing lookups on objects with status
fields.
Anywhere you have code like this:
from dcim.choices import DeviceStatusChoices
from dcim.models import Device
Device.objects.filter(status=DeviceStatusChoices.STATUS_PLANNED)
Update it to this:
from nautobot.extras.models import Status
from nautobot.dcim.models import Device
Device.objects.filter(status=Status.objects.get(slug="planned"))
UUID Primary Database Keys¶
Tip
Primary key (aka ID) fields are no longer auto-incrementing integers and are now randomly-generated UUIDs.
Database keys are now defined as randomly-generated Universally Unique Identifiers (UUIDs) instead of integers, protecting against certain classes of data-traversal attacks.
Merge of UserConfig data into User model¶
There is no longer a distinct UserConfig
model; instead, user configuration and preferences are stored directly on the User
model under the key config_data
.
Custom Fields¶
Tip
You can no longer rename or change the type of a custom field.
Custom Fields have been overhauled for asserting data integrity and improving user experience.
- Custom Fields can no longer be renamed or have their type changed after they have been created.
- Choices for Custom Fields are now stored as discrete
CustomFieldChoice
database objects. Choices that are in active use cannot be deleted.
IPAM Network Field Types¶
Tip
Nautobot 1.2 and later supports most of the same filter-based network membership queries as NetBox. See below and the filtering documentation for more details. (Prior to Nautobot 1.2, IPAM network objects only supported model-manager-based methods for network membership filtering.)
All IPAM objects with network field types (ipam.Aggregate
, ipam.IPAddress
, and ipam.Prefix
) are no longer hard-coded to use PostgreSQL-only inet
or cidr
field types and are now using a custom implementation leveraging SQL-standard varbinary
field types.
Technical Details¶
Below is a summary of the underlying technical changes to network fields. These will be explained in more detail in the following sections.
- For
IPAddress
, theaddress
field was exploded out tohost
,broadcast
, andprefix_length
fields;address
was converted into a computed field. - For
Aggregate
andPrefix
objects, theprefix
field was exploded out tonetwork
,broadcast
, andprefix_length
fields;prefix
was converted into a computed field. - The
host
,network
, andbroadcast
fields are now of avarbinary
database type, which is represented as a packed binary integer (for example, the host1.1.1.1
is packed asb"\x01\x01\x01\x01"
) - Network membership queries are accomplished by triangulating the "position" of an address using the IP, broadcast, and prefix length of the source and target addresses.
Note
You should never have to worry about the binary nature of how the network fields are stored in the database! The Django database ORM takes care of it all!
Changes to IPAddress
¶
The following fields have changed when working with ipam.IPAddress
objects:
address
is now a computed field¶
This field is computed from {host}/{prefix_length}
and is represented as a netaddr.IPNetwork
object.
While this field is now a virtual field, it can still be used in many ways.
It can be used to create objects:
It can be used in .get()
and .filter()
lookups where address
is the primary argument:
>>> IPAddress.objects.get(address="1.1.1.1/30")
IPNetwork('1.1.1.1/30')
>>> IPAddress.objects.filter(address="1.1.1.1/30")
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>]>
Note
If you use a prefix_length
other than /32
(IPv4) or /128
(IPv6) it must be included in your lookups
This field cannot be used in nested filter expressions:
>>> Device.objects.filter(primary_ip4__address="1.1.1.1")
django.core.exceptions.FieldError: Related Field got invalid lookup: address
host
contains the IP address¶
The IP (host) component of the address is now stored in the host
field.
This field can be used in nested filter expressions, for example:
IPAddress prefix_length
contains the prefix length¶
This is an integer, such as 30
for /30
.
For IP addresses with a prefix length other than a host prefix, you will need to filter using host
and prefix_length
fields for greater accuracy.
For example, if you have multiple IPAddress
objects with the same host
value but different prefix_length
:
>>> IPAddress.objects.create(address="1.1.1.1/32")
<IPAddress: 1.1.1.1/32>
>>> IPAddress.objects.filter(host="1.1.1.1")
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>, <IPAddress: 1.1.1.1/32>]>
>>> IPAddress.objects.filter(host="1.1.1.1", prefix_length=30)
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>]>
IPAddress broadcast
contains the broadcast address¶
If the prefix length is that of a host prefix (e.g. /32
), broadcast
will be the same as the host
:
If the prefix length is any larger (e.g. /24
), broadcast
will be that of the containing network for that prefix length (e.g. 1.1.1.255
):
Note
This field is largely for internal use only for facilitating network membership queries and it is not recommend that you use it for filtering.
Changes to Aggregate
and Prefix
¶
The following fields have changed when working with ipam.Aggregate
and ipam.Prefix
objects. These objects share the same field changes.
For these examples we will be using Prefix
objects, but they apply just as equally to Aggregate
objects.
prefix
is now a computed field¶
This field is computed from {network}/{prefix_length}
and is represented as a netaddr.IPNetwork
object.
While this field is now a virtual field, it can still be used in many ways.
It can be used to create objects:
It can be used in .get()
and .filter()
lookups where prefix
is the primary argument:
>>> Prefix.objects.get(prefix="1.1.1.0/24")
<Prefix: 1.1.1.0/24>
>>> Prefix.objects.filter(prefix="1.1.1.0/24")
<PrefixQuerySet [<Prefix: 1.1.1.0/24>]>
network
contains the network address¶
The network component of the address is now stored in the network
field.
Aggregate/Prefix prefix_length
contains the prefix length¶
This is an integer, such as 24
for /24
.
It's highly likely that you will have multiple objects with the same network
address but varying prefix lengths, so you will need to filter using network
and prefix_length
fields for greater accuracy.
For example, if you have multiple Prefix
objects with the same network
value but different prefix_length
:
>>> Prefix.objects.create(prefix="1.1.1.0/25")
<Prefix: 1.1.1.0/25>
>>> Prefix.objects.filter(network="1.1.1.0")
<PrefixQuerySet [<Prefix: 1.1.1.0/24>, <Prefix: 1.1.1.0/25>]>
>>> Prefix.objects.filter(network="1.1.1.0", prefix_length=25)
<PrefixQuerySet [<Prefix: 1.1.1.0/25>]>
Aggregate/Prefix broadcast
contains the broadcast address¶
The broadcast
will be derived from the prefix_length
and will be that of the last network address for that prefix length (e.g. 1.1.1.255
):
Note
This field is largely for internal use only for facilitating network membership queries and it is not recommend that you use it for filtering.
Membership Lookups¶
Nautobot 1.0.x and 1.1.x did not support the custom lookup expressions that NetBox supported for membership queries on IPAM objects (such as Prefix.objects.filter(prefix__net_contained="10.0.0.0/24")
), but instead provided an alternate approach using model manager methods (such as Prefix.objects.net_contained("10.0.0.0/24")
).
In Nautobot 1.2.0 and later, both model manager methods and custom lookup expressions are supported for this purpose, but the latter are now preferred for most use cases and are generally equivalent to their counterparts in NetBox.
Note
Nautobot did not mimic the support of non-subnets for the net_in
query to avoid mistakes and confusion caused by an IP address being mistaken for a /32 as an example.
net_mask_length¶
Returns target addresses matching the source address prefix length.
Note
The NetBox filter net_mask_length should use the prefix_length
field for filtering.
NetBox:
IPAddress.objects.filter(address__net_mask_length=value)
# or
Prefix.objects.filter(prefix__net_mask_length=value)
Nautobot:
REST API Changes¶
The following backwards-incompatible changes have been made to the REST API in Nautobot.
Display field¶
In several endpoints such as device type and VLAN, a display_name
field is used to expose a human friendly string value for the object. This field has been renamed to display
and has been standardized across all model API endpoints.
Custom Field Choices¶
Custom field choices are exposed in Nautobot at a dedicated endpoint: /api/extras/custom-field-choices/
. This replaces the choices
field on on the CustomField model and the subsequent endpoint: /api/extras/custom-fields/