diff --git a/README.md b/README.md index d946215d5..26aa0ccfc 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,5 @@ Please see [the documentation](http://netbox.readthedocs.io/en/stable/) for inst ## Alternative Installations -* [Docker container](https://github.com/digitalocean/netbox-docker) -* [Heroku deployment](https://heroku.com/deploy?template=https://github.com/BILDQUADRAT/netbox/tree/heroku) (via [@mraerino](https://github.com/BILDQUADRAT/netbox/tree/heroku)) -* [Vagrant deployment](https://github.com/ryanmerolle/netbox-vagrant) +* [Docker container](https://github.com/ninech/netbox-docker) (via [@cimnine](https://github.com/cimnine)) +* [Vagrant deployment](https://github.com/ryanmerolle/netbox-vagrant) (via [@ryanmerolle](https://github.com/ryanmerolle)) diff --git a/docs/index.md b/docs/index.md index a661b5e0e..2e8c8b3dc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,6 +6,7 @@ NetBox is an open source web application designed to help manage and document co * **Equipment racks** - Organized by group and site * **Devices** - Types of devices and where they are installed * **Connections** - Network, console, and power connections among devices +* **Virtualization** - Virtual machines and clusters * **Data circuits** - Long-haul communications circuits and providers * **Secrets** - Encrypted storage of sensitive credentials @@ -46,7 +47,7 @@ NetBox is built on the [Django](https://djangoproject.com/) Python framework and | HTTP Service | nginx or Apache | | WSGI Service | gunicorn or uWSGI | | Application | Django/Python | -| Database | PostgreSQL | +| Database | PostgreSQL 9.4+ | # Getting Started diff --git a/docs/installation/ldap.md b/docs/installation/ldap.md index bb1618e32..3cbc0d32c 100644 --- a/docs/installation/ldap.md +++ b/docs/installation/ldap.md @@ -55,7 +55,7 @@ LDAP_IGNORE_CERT_ERRORS = True ## User Authentication !!! info - When using Windows Server, `2012 AUTH_LDAP_USER_DN_TEMPLATE` should be set to None. + When using Windows Server 2012, `AUTH_LDAP_USER_DN_TEMPLATE` should be set to None. ```python from django_auth_ldap.config import LDAPSearch @@ -79,7 +79,7 @@ AUTH_LDAP_USER_ATTR_MAP = { # User Groups for Permissions !!! Info - When using Microsoft Active Directory, Support for nested Groups can be activated by using `GroupOfNamesType()` instead of `NestedGroupOfNamesType()` for AUTH_LDAP_GROUP_TYPE. + When using Microsoft Active Directory, Support for nested Groups can be activated by using `GroupOfNamesType()` instead of `NestedGroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`. ```python from django_auth_ldap.config import LDAPSearch, GroupOfNamesType diff --git a/docs/installation/netbox.md b/docs/installation/netbox.md index b92fa3ad1..530456a56 100644 --- a/docs/installation/netbox.md +++ b/docs/installation/netbox.md @@ -1,18 +1,23 @@ # Installation +This section of the documentation discusses installing and configuring the NetBox application. + +!!! note + Python 3 is strongly encouraged for new installations. Support for Python 2 will be discontinued in the near future. This documentation includes a guide on [migrating from Python 2 to Python 3](migrating-to-python3). + **Ubuntu** Python 3: ```no-highlight -# apt-get install -y python3 python3-dev python3-setuptools libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev zlib1g-dev +# apt-get install -y python3 python3-dev python3-setuptools build-essential libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev zlib1g-dev # easy_install3 pip ``` Python 2: ```no-highlight -# apt-get install -y python2.7 python-dev python-setuptools libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev zlib1g-dev +# apt-get install -y python2.7 python-dev python-setuptools build-essential libxml2-dev libxslt1-dev libffi-dev graphviz libpq-dev libssl-dev zlib1g-dev # easy_install pip ``` @@ -163,13 +168,13 @@ You may use the script located at `netbox/generate_secret_key.py` to generate a # Run Database Migrations !!! warning - The examples on the rest of this page call the `python` executable, which will be Python2 on most systems. Replace this with `python3` if you're running NetBox on Python3. + The examples on the rest of this page call the `python3` executable. Replace this with `python2` or `python` if you're using Python 2. -Before NetBox can run, we need to install the database schema. This is done by running `python manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example): +Before NetBox can run, we need to install the database schema. This is done by running `python3 manage.py migrate` from the `netbox` directory (`/opt/netbox/netbox/` in our example): ```no-highlight # cd /opt/netbox/netbox/ -# python manage.py migrate +# python3 manage.py migrate Operations to perform: Apply all migrations: dcim, sessions, admin, ipam, utilities, auth, circuits, contenttypes, extras, secrets, users Running migrations: @@ -187,7 +192,7 @@ If this step results in a PostgreSQL authentication error, ensure that the usern NetBox does not come with any predefined user accounts. You'll need to create a super user to be able to log into NetBox: ```no-highlight -# python manage.py createsuperuser +# python3 manage.py createsuperuser Username: admin Email address: admin@example.com Password: @@ -198,7 +203,7 @@ Superuser created successfully. # Collect Static Files ```no-highlight -# python manage.py collectstatic --no-input +# python3 manage.py collectstatic --no-input You have requested to collect static files at the destination location as specified in your settings: @@ -219,7 +224,7 @@ NetBox ships with some initial data to help you get started: RIR definitions, co This step is optional. It's perfectly fine to start using NetBox without using this initial data if you'd rather create everything from scratch. ```no-highlight -# python manage.py loaddata initial_data +# python3 manage.py loaddata initial_data Installed 43 object(s) from 4 fixture(s) ``` @@ -228,7 +233,7 @@ Installed 43 object(s) from 4 fixture(s) At this point, NetBox should be able to run. We can verify this by starting a development instance: ```no-highlight -# python manage.py runserver 0.0.0.0:8000 --insecure +# python3 manage.py runserver 0.0.0.0:8000 --insecure Performing system checks... System check identified no issues (0 silenced). diff --git a/docs/installation/postgresql.md b/docs/installation/postgresql.md index 592ba1f38..dd38fec69 100644 --- a/docs/installation/postgresql.md +++ b/docs/installation/postgresql.md @@ -1,16 +1,16 @@ -NetBox requires a PostgreSQL 9.4 or higher database to store data. (Please note that MySQL is not supported, as NetBox leverages PostgreSQL's built-in [network address types](https://www.postgresql.org/docs/current/static/datatype-net-types.html).) +NetBox requires a PostgreSQL database to store data. This can be hosted locally or on a remote server. (Please note that MySQL is not supported, as NetBox leverages PostgreSQL's built-in [network address types](https://www.postgresql.org/docs/current/static/datatype-net-types.html).) !!! note - The installation instructions provided here have been tested to work on Ubuntu 16.04 and CentOS 6.9. The particular commands needed to install dependencies on other distributions may vary significantly. Unfortunately, this is outside the control of the NetBox maintainers. Please consult your distribution's documentation for assistance with any errors. + The installation instructions provided here have been tested to work on Ubuntu 16.04 and CentOS 7.4. The particular commands needed to install dependencies on other distributions may vary significantly. Unfortunately, this is outside the control of the NetBox maintainers. Please consult your distribution's documentation for assistance with any errors. !!! warning - NetBox v2.2 or later requires PostgreSQL 9.4.0 or higher. + NetBox v2.2 and later requires PostgreSQL 9.4 or higher. # Installation **Ubuntu** -If a recent enough version of PostgreSQL is not available through your distribution's package manager, consider installing from an official [PostgreSQL repository](https://wiki.postgresql.org/wiki/Apt). +If a recent enough version of PostgreSQL is not available through your distribution's package manager, you'll need to install it from an official [PostgreSQL repository](https://wiki.postgresql.org/wiki/Apt). ```no-highlight # apt-get update @@ -19,22 +19,26 @@ If a recent enough version of PostgreSQL is not available through your distribut **CentOS** +CentOS 7.4 does not ship with a recent enough version of PostgreSQL, so it will need to be installed from an external repository. The instructions below show the installation of PostgreSQL 9.6. + ```no-highlight -# yum install -y postgresql postgresql-server postgresql-devel -# postgresql-setup initdb +# yum install https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm +# yum install postgresql96 postgresql96-server postgresql96-devel +# /usr/pgsql-9.6/bin/postgresql96-setup initdb ``` -CentOS users should modify the PostgreSQL configuration to accept password-based authentication by replacing `ident` with `md5` for all host entries within `/var/lib/pgsql/data/pg_hba.conf`. For example: +CentOS users should modify the PostgreSQL configuration to accept password-based authentication by replacing `ident` with `md5` for all host entries within `/var/lib/pgsql/9.6/data/pg_hba.conf`. For example: ```no-highlight host all all 127.0.0.1/32 md5 host all all ::1/128 md5 ``` -Then, start the service: +Then, start the service and enable it to run at boot: ```no-highlight -# systemctl start postgresql +# systemctl start postgresql-9.6 +# systemctl enable postgresql-9.6 ``` # Database Creation @@ -58,10 +62,10 @@ GRANT postgres=# \q ``` -You can verify that authentication works issuing the following command and providing the configured password: +You can verify that authentication works issuing the following command and providing the configured password. (Replace `localhost` with your database server if using a remote database.) ```no-highlight -# psql -U netbox -h localhost -W +# psql -U netbox -W -h localhost netbox ``` -If successful, you will enter a `postgres` prompt. Type `\q` to exit. +If successful, you will enter a `netbox` prompt. Type `\q` to exit. diff --git a/docs/miscellaneous/reports.md b/docs/miscellaneous/reports.md index c6369283c..aae258f00 100644 --- a/docs/miscellaneous/reports.md +++ b/docs/miscellaneous/reports.md @@ -117,3 +117,13 @@ Our example report above would be called as: ``` POST /api/extras/reports/devices.DeviceConnectionsReport/run/ ``` + +### Via the CLI + +Reports can be run on the CLI by invoking the management command: + +``` +python3 manage.py runreport +``` + +One or more report modules may be specified. diff --git a/docs/shell/intro.md b/docs/miscellaneous/shell.md similarity index 100% rename from docs/shell/intro.md rename to docs/miscellaneous/shell.md diff --git a/mkdocs.yml b/mkdocs.yml index e3ac9f50b..430a05ac7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,10 +25,9 @@ pages: - 'Authentication': 'api/authentication.md' - 'Working with Secrets': 'api/working-with-secrets.md' - 'Examples': 'api/examples.md' - - 'Shell': - - 'Introduction': 'shell/intro.md' - 'Miscellaneous': - 'Reports': 'miscellaneous/reports.md' + - 'Shell': 'miscellaneous/shell.md' - 'Development': - 'Utility Views': 'development/utility-views.md' diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 820477e24..f0b0f6d52 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -17,6 +17,7 @@ from utilities.forms import ( ExpandableNameField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField, FilterTreeNodeMultipleChoiceField, ) +from virtualization.models import Cluster from .formfields import MACAddressFormField from .models import ( DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, ConsolePort, @@ -900,11 +901,20 @@ class DeviceCSVForm(BaseDeviceCSVForm): required=False, help_text='Mounted rack face' ) + cluster = forms.ModelChoiceField( + queryset=Cluster.objects.all(), + to_field_name='name', + required=False, + help_text='Virtualization cluster', + error_messages={ + 'invalid_choice': 'Invalid cluster name.', + } + ) class Meta(BaseDeviceCSVForm.Meta): fields = [ 'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status', - 'site', 'rack_group', 'rack_name', 'position', 'face', + 'site', 'rack_group', 'rack_name', 'position', 'face', 'cluster', ] def clean(self): @@ -940,11 +950,19 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm): device_bay_name = forms.CharField( help_text='Name of device bay', ) + cluster = forms.ModelChoiceField( + queryset=Cluster.objects.all(), + to_field_name='name', + help_text='Virtualization cluster', + error_messages={ + 'invalid_choice': 'Invalid cluster name.', + } + ) class Meta(BaseDeviceCSVForm.Meta): fields = [ 'name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'asset_tag', 'status', - 'parent', 'device_bay_name', + 'parent', 'device_bay_name', 'cluster', ] def clean(self): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 387af4b61..13710c310 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -34,32 +34,6 @@ from .models import ( ) -EXPANSION_PATTERN = '\[(\d+-\d+)\]' - - -def xstr(s): - """ - Replace None with an empty string (for CSV export) - """ - return '' if s is None else str(s) - - -def expand_pattern(string): - """ - Expand a numeric pattern into a list of strings. Examples: - 'ge-0/0/[0-3]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3'] - 'xe-0/[0-3]/[0-7]' => ['xe-0/0/0', 'xe-0/0/1', 'xe-0/0/2', ... 'xe-0/3/5', 'xe-0/3/6', 'xe-0/3/7'] - """ - lead, pattern, remnant = re.split(EXPANSION_PATTERN, string, maxsplit=1) - x, y = pattern.split('-') - for i in range(int(x), int(y) + 1): - if remnant: - for string in expand_pattern(remnant): - yield "{0}{1}{2}".format(lead, i, string) - else: - yield "{0}{1}".format(lead, i) - - class BulkDisconnectView(View): """ An extendable view for disconnection console/power/interface components in bulk. diff --git a/netbox/extras/management/commands/runreport.py b/netbox/extras/management/commands/runreport.py index 9adf6b130..05083b7f7 100644 --- a/netbox/extras/management/commands/runreport.py +++ b/netbox/extras/management/commands/runreport.py @@ -12,7 +12,6 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('reports', nargs='+', help="Report(s) to run") - # parser.add_argument('--verbose', action='store_true', default=False, help="Print all logs") def handle(self, *args, **options): @@ -22,7 +21,7 @@ class Command(BaseCommand): # Run reports for module_name, report_list in reports: for report in report_list: - if module_name in options['reports'] or report.full_namel in options['reports']: + if module_name in options['reports'] or report.full_name in options['reports']: # Run the report and create a new ReportResult self.stdout.write( diff --git a/netbox/extras/migrations/0008_reports.py b/netbox/extras/migrations/0008_reports.py index c9fc16cc3..83565cce7 100644 --- a/netbox/extras/migrations/0008_reports.py +++ b/netbox/extras/migrations/0008_reports.py @@ -1,11 +1,30 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.4 on 2017-09-26 21:25 from __future__ import unicode_literals +from distutils.version import StrictVersion from django.conf import settings import django.contrib.postgres.fields.jsonb -from django.db import migrations, models +from django.db import connection, migrations, models import django.db.models.deletion +from django.db.utils import OperationalError + + +def verify_postgresql_version(apps, schema_editor): + """ + Verify that PostgreSQL is version 9.4 or higher. + """ + try: + with connection.cursor() as cursor: + cursor.execute("SELECT VERSION()") + row = cursor.fetchone() + pg_version = row[0].split()[1] + if StrictVersion(pg_version) < StrictVersion('9.4.0'): + raise Exception("PostgreSQL 9.4.0 or higher is required ({} found). Upgrade PostgreSQL and then run migrations again.".format(pg_version)) + + # Skip if the database is missing (e.g. for CI testing) or misconfigured. + except OperationalError: + pass class Migration(migrations.Migration): @@ -16,6 +35,7 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RunPython(verify_postgresql_version), migrations.CreateModel( name='ReportResult', fields=[ diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 5181e88e9..1e7a73166 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -274,6 +274,7 @@ class TopologyMap(models.Model): # Construct the graph graph = graphviz.Graph() graph.graph_attr['ranksep'] = '1' + seen = set() for i, device_set in enumerate(self.device_sets): subgraph = graphviz.Graph(name='sg{}'.format(i)) @@ -288,6 +289,9 @@ class TopologyMap(models.Model): devices = [] for query in device_set.strip(';').split(';'): # Split regexes on semicolons devices += Device.objects.filter(name__regex=query).select_related('device_role') + # Remove duplicate devices + devices = [d for d in devices if d.id not in seen] + seen.update([d.id for d in devices]) for d in devices: bg_color = '#{}'.format(d.device_role.color) fg_color = '#{}'.format(foreground_color(d.device_role.color)) diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index a5b64fca6..36394dad2 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -240,12 +240,22 @@ class WritablePrefixSerializer(CustomFieldModelSerializer): # IP addresses # +class IPAddressInterfaceSerializer(InterfaceSerializer): + virtual_machine = NestedVirtualMachineSerializer() + + class Meta(InterfaceSerializer.Meta): + fields = [ + 'id', 'device', 'virtual_machine', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', + 'mgmt_only', 'description', 'is_connected', 'interface_connection', 'circuit_termination', + ] + + class IPAddressSerializer(CustomFieldModelSerializer): vrf = NestedVRFSerializer() tenant = NestedTenantSerializer() status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES) role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES) - interface = InterfaceSerializer() + interface = IPAddressInterfaceSerializer() class Meta: model = IPAddress @@ -262,6 +272,7 @@ class NestedIPAddressSerializer(serializers.ModelSerializer): model = IPAddress fields = ['id', 'url', 'family', 'address'] + IPAddressSerializer._declared_fields['nat_inside'] = NestedIPAddressSerializer() IPAddressSerializer._declared_fields['nat_outside'] = NestedIPAddressSerializer() diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index dabb518ae..b615b470f 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -151,7 +151,7 @@ class IPAddressViewSet(WritableSerializerMixin, CustomFieldModelViewSet): queryset = IPAddress.objects.select_related( 'vrf__tenant', 'tenant', 'nat_inside' ).prefetch_related( - 'interface__device' + 'interface__device', 'interface__virtual_machine' ) serializer_class = serializers.IPAddressSerializer write_serializer_class = serializers.WritableIPAddressSerializer diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 791ba1b1a..73a522d63 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -440,7 +440,7 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel): self.get_status_display(), self.get_role_display(), self.device.identifier if self.device else None, - self.virtual_machine.name if self.device else None, + self.virtual_machine.name if self.virtual_machine else None, self.interface.name if self.interface else None, is_primary, self.description, @@ -452,6 +452,12 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel): return self.interface.device return None + @property + def virtual_machine(self): + if self.interface: + return self.interface.virtual_machine + return None + def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] diff --git a/netbox/netbox/__init__.py b/netbox/netbox/__init__.py index 361659e58..e69de29bb 100644 --- a/netbox/netbox/__init__.py +++ b/netbox/netbox/__init__.py @@ -1,18 +0,0 @@ -from distutils.version import StrictVersion - -from django.db import connection -from django.db.utils import OperationalError - - -# NetBox v2.2 and later requires PostgreSQL 9.4 or higher. -try: - with connection.cursor() as cursor: - cursor.execute("SELECT VERSION()") - row = cursor.fetchone() - pg_version = row[0].split()[1] - if StrictVersion(pg_version) < StrictVersion('9.4.0'): - raise Exception("PostgreSQL 9.4.0 or higher is required. ({} found)".format(pg_version)) - -# Skip if the database is missing (e.g. for CI testing) or misconfigured. -except OperationalError: - pass diff --git a/netbox/netbox/forms.py b/netbox/netbox/forms.py index 72a3ab8de..0521f2d2f 100644 --- a/netbox/netbox/forms.py +++ b/netbox/netbox/forms.py @@ -30,6 +30,10 @@ OBJ_TYPE_CHOICES = ( ('Tenancy', ( ('tenant', 'Tenants'), )), + ('Virtualization', ( + ('cluster', 'Clusters'), + ('virtualmachine', 'Virtual machines'), + )), ) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 7272da8ae..be6311d8b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -13,7 +13,7 @@ except ImportError: ) -VERSION = '2.2.0-dev' +VERSION = '2.2.3-dev' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index 5ec81cb25..f41ff53c2 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -27,7 +27,7 @@ from tenancy.models import Tenant from tenancy.tables import TenantTable from virtualization.filters import ClusterFilter, VirtualMachineFilter from virtualization.models import Cluster, VirtualMachine -from virtualization.tables import ClusterTable, VirtualMachineTable +from virtualization.tables import ClusterTable, VirtualMachineDetailTable from .forms import SearchForm @@ -126,9 +126,11 @@ SEARCH_TYPES = OrderedDict(( 'url': 'virtualization:cluster_list', }), ('virtualmachine', { - 'queryset': VirtualMachine.objects.select_related('cluster', 'tenant', 'platform'), + 'queryset': VirtualMachine.objects.select_related( + 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', + ), 'filter': VirtualMachineFilter, - 'table': VirtualMachineTable, + 'table': VirtualMachineDetailTable, 'url': 'virtualization:virtualmachine_list', }), )) diff --git a/netbox/templates/500.html b/netbox/templates/500.html index 575694b13..2857325b9 100644 --- a/netbox/templates/500.html +++ b/netbox/templates/500.html @@ -3,9 +3,10 @@ - Server Error - + Server Error + + diff --git a/netbox/templates/_base.html b/netbox/templates/_base.html index 1a187a681..17efcadd7 100644 --- a/netbox/templates/_base.html +++ b/netbox/templates/_base.html @@ -3,12 +3,13 @@ - {% block title %}Home{% endblock %} - NetBox - + {% block title %}Home{% endblock %} - NetBox + - + + diff --git a/netbox/templates/virtualization/cluster_add_devices.html b/netbox/templates/virtualization/cluster_add_devices.html index 2029ea5b8..0a792e0c4 100644 --- a/netbox/templates/virtualization/cluster_add_devices.html +++ b/netbox/templates/virtualization/cluster_add_devices.html @@ -59,6 +59,7 @@