1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge branch 'develop' into develop-2.9

This commit is contained in:
Jeremy Stretch
2020-07-21 12:57:02 -04:00
20 changed files with 214 additions and 96 deletions

View File

@@ -1,3 +1,4 @@
import socket
from collections import OrderedDict
from django.conf import settings
@@ -388,6 +389,22 @@ class DeviceViewSet(CustomFieldModelViewSet):
device.platform
))
# Check for primary IP address from NetBox object
if device.primary_ip:
host = str(device.primary_ip.address.ip)
else:
# Raise exception for no IP address and no Name if device.name does not exist
if not device.name:
raise ServiceUnavailable(
"This device does not have a primary IP address or device name to lookup configured.")
try:
# Attempt to complete a DNS name resolution if no primary_ip is set
host = socket.gethostbyname(device.name)
except socket.gaierror:
# Name lookup failure
raise ServiceUnavailable(
f"Name lookup failure, unable to resolve IP address for {device.name}. Please set Primary IP or setup name resolution.")
# Check that NAPALM is installed
try:
import napalm
@@ -407,10 +424,8 @@ class DeviceViewSet(CustomFieldModelViewSet):
if not request.user.has_perm('dcim.napalm_read_device'):
return HttpResponseForbidden()
# Connect to the device
napalm_methods = request.GET.getlist('method')
response = OrderedDict([(m, None) for m in napalm_methods])
ip_address = str(device.primary_ip.address.ip)
username = settings.NAPALM_USERNAME
password = settings.NAPALM_PASSWORD
optional_args = settings.NAPALM_ARGS.copy()
@@ -430,8 +445,9 @@ class DeviceViewSet(CustomFieldModelViewSet):
elif key:
optional_args[key.lower()] = request.headers[header]
# Connect to the device
d = driver(
hostname=ip_address,
hostname=host,
username=username,
password=password,
timeout=settings.NAPALM_TIMEOUT,
@@ -440,7 +456,7 @@ class DeviceViewSet(CustomFieldModelViewSet):
try:
d.open()
except Exception as e:
raise ServiceUnavailable("Error connecting to the device at {}: {}".format(ip_address, e))
raise ServiceUnavailable("Error connecting to the device at {}: {}".format(host, e))
# Validate and execute each specified NAPALM method
for method in napalm_methods:

View File

@@ -7,13 +7,17 @@ from utilities.choices import ChoiceSet
class SiteStatusChoices(ChoiceSet):
STATUS_ACTIVE = 'active'
STATUS_PLANNED = 'planned'
STATUS_STAGING = 'staging'
STATUS_ACTIVE = 'active'
STATUS_DECOMMISSIONING = 'decommissioning'
STATUS_RETIRED = 'retired'
CHOICES = (
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
(STATUS_STAGING, 'Staging'),
(STATUS_ACTIVE, 'Active'),
(STATUS_DECOMMISSIONING, 'Decommissioning'),
(STATUS_RETIRED, 'Retired'),
)
@@ -228,6 +232,11 @@ class PowerPortTypeChoices(ChoiceSet):
TYPE_NEMA_1430P = 'nema-14-30p'
TYPE_NEMA_1450P = 'nema-14-50p'
TYPE_NEMA_1460P = 'nema-14-60p'
TYPE_NEMA_1515P = 'nema-15-15p'
TYPE_NEMA_1520P = 'nema-15-20p'
TYPE_NEMA_1530P = 'nema-15-30p'
TYPE_NEMA_1550P = 'nema-15-50p'
TYPE_NEMA_1560P = 'nema-15-60p'
# NEMA locking
TYPE_NEMA_L115P = 'nema-l1-15p'
TYPE_NEMA_L515P = 'nema-l5-15p'
@@ -243,6 +252,10 @@ class PowerPortTypeChoices(ChoiceSet):
TYPE_NEMA_L1430P = 'nema-l14-30p'
TYPE_NEMA_L1450P = 'nema-l14-50p'
TYPE_NEMA_L1460P = 'nema-l14-60p'
TYPE_NEMA_L1520P = 'nema-l15-20p'
TYPE_NEMA_L1530P = 'nema-l15-30p'
TYPE_NEMA_L1550P = 'nema-l15-50p'
TYPE_NEMA_L1560P = 'nema-l15-60p'
TYPE_NEMA_L2120P = 'nema-l21-20p'
TYPE_NEMA_L2130P = 'nema-l21-30p'
# California style
@@ -304,6 +317,11 @@ class PowerPortTypeChoices(ChoiceSet):
(TYPE_NEMA_1430P, 'NEMA 14-30P'),
(TYPE_NEMA_1450P, 'NEMA 14-50P'),
(TYPE_NEMA_1460P, 'NEMA 14-60P'),
(TYPE_NEMA_1515P, 'NEMA 15-15P'),
(TYPE_NEMA_1520P, 'NEMA 15-20P'),
(TYPE_NEMA_1530P, 'NEMA 15-30P'),
(TYPE_NEMA_1550P, 'NEMA 15-50P'),
(TYPE_NEMA_1560P, 'NEMA 15-60P'),
)),
('NEMA (Locking)', (
(TYPE_NEMA_L115P, 'NEMA L1-15P'),
@@ -320,6 +338,10 @@ class PowerPortTypeChoices(ChoiceSet):
(TYPE_NEMA_L1430P, 'NEMA L14-30P'),
(TYPE_NEMA_L1450P, 'NEMA L14-50P'),
(TYPE_NEMA_L1460P, 'NEMA L14-60P'),
(TYPE_NEMA_L1520P, 'NEMA L15-20P'),
(TYPE_NEMA_L1530P, 'NEMA L15-30P'),
(TYPE_NEMA_L1550P, 'NEMA L15-50P'),
(TYPE_NEMA_L1560P, 'NEMA L15-60P'),
(TYPE_NEMA_L2120P, 'NEMA L21-20P'),
(TYPE_NEMA_L2130P, 'NEMA L21-30P'),
)),
@@ -389,6 +411,11 @@ class PowerOutletTypeChoices(ChoiceSet):
TYPE_NEMA_1430R = 'nema-14-30r'
TYPE_NEMA_1450R = 'nema-14-50r'
TYPE_NEMA_1460R = 'nema-14-60r'
TYPE_NEMA_1515R = 'nema-15-15r'
TYPE_NEMA_1520R = 'nema-15-20r'
TYPE_NEMA_1530R = 'nema-15-30r'
TYPE_NEMA_1550R = 'nema-15-50r'
TYPE_NEMA_1560R = 'nema-15-60r'
# NEMA locking
TYPE_NEMA_L115R = 'nema-l1-15r'
TYPE_NEMA_L515R = 'nema-l5-15r'
@@ -404,6 +431,10 @@ class PowerOutletTypeChoices(ChoiceSet):
TYPE_NEMA_L1430R = 'nema-l14-30r'
TYPE_NEMA_L1450R = 'nema-l14-50r'
TYPE_NEMA_L1460R = 'nema-l14-60r'
TYPE_NEMA_L1520R = 'nema-l15-20r'
TYPE_NEMA_L1530R = 'nema-l15-30r'
TYPE_NEMA_L1550R = 'nema-l15-50r'
TYPE_NEMA_L1560R = 'nema-l15-60r'
TYPE_NEMA_L2120R = 'nema-l21-20r'
TYPE_NEMA_L2130R = 'nema-l21-30r'
# California style
@@ -466,6 +497,11 @@ class PowerOutletTypeChoices(ChoiceSet):
(TYPE_NEMA_1430R, 'NEMA 14-30R'),
(TYPE_NEMA_1450R, 'NEMA 14-50R'),
(TYPE_NEMA_1460R, 'NEMA 14-60R'),
(TYPE_NEMA_1515R, 'NEMA 15-15R'),
(TYPE_NEMA_1520R, 'NEMA 15-20R'),
(TYPE_NEMA_1530R, 'NEMA 15-30R'),
(TYPE_NEMA_1550R, 'NEMA 15-50R'),
(TYPE_NEMA_1560R, 'NEMA 15-60R'),
)),
('NEMA (Locking)', (
(TYPE_NEMA_L115R, 'NEMA L1-15R'),
@@ -482,6 +518,10 @@ class PowerOutletTypeChoices(ChoiceSet):
(TYPE_NEMA_L1430R, 'NEMA L14-30R'),
(TYPE_NEMA_L1450R, 'NEMA L14-50R'),
(TYPE_NEMA_L1460R, 'NEMA L14-60R'),
(TYPE_NEMA_L1520R, 'NEMA L15-20R'),
(TYPE_NEMA_L1530R, 'NEMA L15-30R'),
(TYPE_NEMA_L1550R, 'NEMA L15-50R'),
(TYPE_NEMA_L1560R, 'NEMA L15-60R'),
(TYPE_NEMA_L2120R, 'NEMA L21-20R'),
(TYPE_NEMA_L2130R, 'NEMA L21-30R'),
)),

View File

@@ -804,7 +804,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
)
if power_stats:
allocated_draw_total = sum(x['allocated_draw_total'] for x in power_stats)
allocated_draw_total = sum(x['allocated_draw_total'] or 0 for x in power_stats)
available_power_total = sum(x['available_power'] for x in power_stats)
return int(allocated_draw_total / available_power_total * 100) or 0
return 0

View File

@@ -52,20 +52,12 @@ RACK_DEVICE_COUNT = """
<a href="{% url 'dcim:device_list' %}?rack_id={{ record.pk }}">{{ value }}</a>
"""
DEVICEROLE_DEVICE_COUNT = """
<a href="{% url 'dcim:device_list' %}?role={{ record.slug }}">{{ value }}</a>
DEVICE_COUNT = """
<a href="{% url 'dcim:device_list' %}?role={{ record.slug }}">{{ value|default:0 }}</a>
"""
DEVICEROLE_VM_COUNT = """
<a href="{% url 'virtualization:virtualmachine_list' %}?role={{ record.slug }}">{{ value }}</a>
"""
PLATFORM_DEVICE_COUNT = """
<a href="{% url 'dcim:device_list' %}?platform={{ record.slug }}">{{ value }}</a>
"""
PLATFORM_VM_COUNT = """
<a href="{% url 'virtualization:virtualmachine_list' %}?platform={{ record.slug }}">{{ value }}</a>
VM_COUNT = """
<a href="{% url 'virtualization:virtualmachine_list' %}?role={{ record.slug }}">{{ value|default:0 }}</a>
"""
STATUS_LABEL = """
@@ -210,6 +202,7 @@ class RackGroupTable(BaseTable):
class RackRoleTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(linkify=True)
rack_count = tables.Column(verbose_name='Racks')
color = tables.TemplateColumn(COLOR_LABEL)
actions = ButtonsColumn(RackRole)
@@ -502,15 +495,11 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
class DeviceRoleTable(BaseTable):
pk = ToggleColumn()
device_count = tables.TemplateColumn(
template_code=DEVICEROLE_DEVICE_COUNT,
accessor=Accessor('devices__unrestricted__count'),
orderable=False,
template_code=DEVICE_COUNT,
verbose_name='Devices'
)
vm_count = tables.TemplateColumn(
template_code=DEVICEROLE_VM_COUNT,
accessor=Accessor('virtual_machines__unrestricted__count'),
orderable=False,
template_code=VM_COUNT,
verbose_name='VMs'
)
color = tables.TemplateColumn(
@@ -533,15 +522,11 @@ class DeviceRoleTable(BaseTable):
class PlatformTable(BaseTable):
pk = ToggleColumn()
device_count = tables.TemplateColumn(
template_code=PLATFORM_DEVICE_COUNT,
accessor=Accessor('devices__unrestricted__count'),
orderable=False,
template_code=DEVICE_COUNT,
verbose_name='Devices'
)
vm_count = tables.TemplateColumn(
template_code=PLATFORM_VM_COUNT,
accessor=Accessor('virtual_machines__unrestricted__count'),
orderable=False,
template_code=VM_COUNT,
verbose_name='VMs'
)
actions = ButtonsColumn(Platform, pk_field='slug')

View File

@@ -22,7 +22,7 @@ from secrets.models import Secret
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator
from utilities.permissions import get_permission_for_model
from utilities.utils import csv_format
from utilities.utils import csv_format, get_subquery
from utilities.views import (
BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, BulkRenameView, ComponentCreateView,
GetReturnURLMixin, ObjectView, ObjectImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
@@ -341,13 +341,14 @@ class RackView(ObjectView):
def get(self, request, pk):
rack = get_object_or_404(self.queryset, pk=pk)
nonracked_devices = Device.objects.restrict(request.user, 'view').filter(
# Get 0U and child devices located within the rack
nonracked_devices = Device.objects.filter(
rack=rack,
position__isnull=True,
parent_bay__isnull=True
position__isnull=True
).prefetch_related('device_type__manufacturer')
peer_racks = Rack.objects.restrict(request.user, 'view').filter(site=rack.site)
if rack.group:
peer_racks = peer_racks.filter(group=rack.group)
else:
@@ -474,10 +475,10 @@ class RackReservationBulkDeleteView(BulkDeleteView):
class ManufacturerListView(ObjectListView):
queryset = Manufacturer.objects.annotate(
devicetype_count=Count('device_types', distinct=True),
inventoryitem_count=Count('inventory_items', distinct=True),
platform_count=Count('platforms', distinct=True),
).order_by(*Manufacturer._meta.ordering)
devicetype_count=get_subquery(DeviceType, 'manufacturer'),
inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
platform_count=get_subquery(Platform, 'manufacturer')
)
table = tables.ManufacturerTable
@@ -919,7 +920,10 @@ class DeviceBayTemplateBulkDeleteView(BulkDeleteView):
#
class DeviceRoleListView(ObjectListView):
queryset = DeviceRole.objects.all()
queryset = DeviceRole.objects.annotate(
device_count=get_subquery(Device, 'device_role'),
vm_count=get_subquery(VirtualMachine, 'role')
)
table = tables.DeviceRoleTable
@@ -948,7 +952,10 @@ class DeviceRoleBulkDeleteView(BulkDeleteView):
#
class PlatformListView(ObjectListView):
queryset = Platform.objects.all()
queryset = Platform.objects.annotate(
device_count=get_subquery(Device, 'device_role'),
vm_count=get_subquery(VirtualMachine, 'role')
)
table = tables.PlatformTable