views.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. import socket
  2. from collections import OrderedDict
  3. from django.http import Http404, HttpResponse, HttpResponseForbidden
  4. from django.shortcuts import get_object_or_404
  5. from drf_yasg import openapi
  6. from drf_yasg.openapi import Parameter
  7. from drf_yasg.utils import swagger_auto_schema
  8. from rest_framework.decorators import action
  9. from rest_framework.response import Response
  10. from rest_framework.routers import APIRootView
  11. from rest_framework.viewsets import ViewSet
  12. from circuits.models import Circuit
  13. from dcim import filtersets
  14. from dcim.models import *
  15. from extras.api.views import ConfigContextQuerySetMixin
  16. from ipam.models import Prefix, VLAN
  17. from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
  18. from netbox.api.exceptions import ServiceUnavailable
  19. from netbox.api.metadata import ContentTypeMetadata
  20. from netbox.api.pagination import StripCountAnnotationsPaginator
  21. from netbox.api.viewsets import NetBoxModelViewSet
  22. from netbox.config import get_config
  23. from utilities.api import get_serializer_for_model
  24. from utilities.utils import count_related
  25. from virtualization.models import VirtualMachine
  26. from . import serializers
  27. from .exceptions import MissingFilterException
  28. class DCIMRootView(APIRootView):
  29. """
  30. DCIM API root view
  31. """
  32. def get_view_name(self):
  33. return 'DCIM'
  34. # Mixins
  35. class PathEndpointMixin(object):
  36. @action(detail=True, url_path='trace')
  37. def trace(self, request, pk):
  38. """
  39. Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).
  40. """
  41. obj = get_object_or_404(self.queryset, pk=pk)
  42. # Initialize the path array
  43. path = []
  44. if request.GET.get('render', None) == 'svg':
  45. # Render SVG
  46. try:
  47. width = min(int(request.GET.get('width')), 1600)
  48. except (ValueError, TypeError):
  49. width = None
  50. drawing = obj.get_trace_svg(
  51. base_url=request.build_absolute_uri('/'),
  52. width=width
  53. )
  54. return HttpResponse(drawing.tostring(), content_type='image/svg+xml')
  55. for near_end, cable, far_end in obj.trace():
  56. if near_end is None:
  57. # Split paths
  58. break
  59. # Serialize each object
  60. serializer_a = get_serializer_for_model(near_end, prefix='Nested')
  61. x = serializer_a(near_end, context={'request': request}).data
  62. if cable is not None:
  63. y = serializers.TracedCableSerializer(cable, context={'request': request}).data
  64. else:
  65. y = None
  66. if far_end is not None:
  67. serializer_b = get_serializer_for_model(far_end, prefix='Nested')
  68. z = serializer_b(far_end, context={'request': request}).data
  69. else:
  70. z = None
  71. path.append((x, y, z))
  72. return Response(path)
  73. class PassThroughPortMixin(object):
  74. @action(detail=True, url_path='paths')
  75. def paths(self, request, pk):
  76. """
  77. Return all CablePaths which traverse a given pass-through port.
  78. """
  79. obj = get_object_or_404(self.queryset, pk=pk)
  80. cablepaths = CablePath.objects.filter(path__contains=obj).prefetch_related('origin', 'destination')
  81. serializer = serializers.CablePathSerializer(cablepaths, context={'request': request}, many=True)
  82. return Response(serializer.data)
  83. #
  84. # Regions
  85. #
  86. class RegionViewSet(NetBoxModelViewSet):
  87. queryset = Region.objects.add_related_count(
  88. Region.objects.all(),
  89. Site,
  90. 'region',
  91. 'site_count',
  92. cumulative=True
  93. ).prefetch_related('tags')
  94. serializer_class = serializers.RegionSerializer
  95. filterset_class = filtersets.RegionFilterSet
  96. #
  97. # Site groups
  98. #
  99. class SiteGroupViewSet(NetBoxModelViewSet):
  100. queryset = SiteGroup.objects.add_related_count(
  101. SiteGroup.objects.all(),
  102. Site,
  103. 'group',
  104. 'site_count',
  105. cumulative=True
  106. ).prefetch_related('tags')
  107. serializer_class = serializers.SiteGroupSerializer
  108. filterset_class = filtersets.SiteGroupFilterSet
  109. #
  110. # Sites
  111. #
  112. class SiteViewSet(NetBoxModelViewSet):
  113. queryset = Site.objects.prefetch_related(
  114. 'region', 'tenant', 'asns', 'tags'
  115. ).annotate(
  116. device_count=count_related(Device, 'site'),
  117. rack_count=count_related(Rack, 'site'),
  118. prefix_count=count_related(Prefix, 'site'),
  119. vlan_count=count_related(VLAN, 'site'),
  120. circuit_count=count_related(Circuit, 'terminations__site'),
  121. virtualmachine_count=count_related(VirtualMachine, 'cluster__site')
  122. )
  123. serializer_class = serializers.SiteSerializer
  124. filterset_class = filtersets.SiteFilterSet
  125. #
  126. # Locations
  127. #
  128. class LocationViewSet(NetBoxModelViewSet):
  129. queryset = Location.objects.add_related_count(
  130. Location.objects.add_related_count(
  131. Location.objects.all(),
  132. Device,
  133. 'location',
  134. 'device_count',
  135. cumulative=True
  136. ),
  137. Rack,
  138. 'location',
  139. 'rack_count',
  140. cumulative=True
  141. ).prefetch_related('site', 'tags')
  142. serializer_class = serializers.LocationSerializer
  143. filterset_class = filtersets.LocationFilterSet
  144. #
  145. # Rack roles
  146. #
  147. class RackRoleViewSet(NetBoxModelViewSet):
  148. queryset = RackRole.objects.prefetch_related('tags').annotate(
  149. rack_count=count_related(Rack, 'role')
  150. )
  151. serializer_class = serializers.RackRoleSerializer
  152. filterset_class = filtersets.RackRoleFilterSet
  153. #
  154. # Racks
  155. #
  156. class RackViewSet(NetBoxModelViewSet):
  157. queryset = Rack.objects.prefetch_related(
  158. 'site', 'location', 'role', 'tenant', 'tags'
  159. ).annotate(
  160. device_count=count_related(Device, 'rack'),
  161. powerfeed_count=count_related(PowerFeed, 'rack')
  162. )
  163. serializer_class = serializers.RackSerializer
  164. filterset_class = filtersets.RackFilterSet
  165. @swagger_auto_schema(
  166. responses={200: serializers.RackUnitSerializer(many=True)},
  167. query_serializer=serializers.RackElevationDetailFilterSerializer
  168. )
  169. @action(detail=True)
  170. def elevation(self, request, pk=None):
  171. """
  172. Rack elevation representing the list of rack units. Also supports rendering the elevation as an SVG.
  173. """
  174. rack = get_object_or_404(self.queryset, pk=pk)
  175. serializer = serializers.RackElevationDetailFilterSerializer(data=request.GET)
  176. if not serializer.is_valid():
  177. return Response(serializer.errors, 400)
  178. data = serializer.validated_data
  179. if data['render'] == 'svg':
  180. # Determine attributes for highlighting devices (if any)
  181. highlight_params = []
  182. for param in request.GET.getlist('highlight'):
  183. try:
  184. highlight_params.append(param.split(':', 1))
  185. except ValueError:
  186. pass
  187. # Render and return the elevation as an SVG drawing with the correct content type
  188. drawing = rack.get_elevation_svg(
  189. face=data['face'],
  190. user=request.user,
  191. unit_width=data['unit_width'],
  192. unit_height=data['unit_height'],
  193. legend_width=data['legend_width'],
  194. include_images=data['include_images'],
  195. base_url=request.build_absolute_uri('/'),
  196. highlight_params=highlight_params
  197. )
  198. return HttpResponse(drawing.tostring(), content_type='image/svg+xml')
  199. else:
  200. # Return a JSON representation of the rack units in the elevation
  201. elevation = rack.get_rack_units(
  202. face=data['face'],
  203. user=request.user,
  204. exclude=data['exclude'],
  205. expand_devices=data['expand_devices']
  206. )
  207. # Enable filtering rack units by ID
  208. q = data['q']
  209. if q:
  210. elevation = [u for u in elevation if q in str(u['id']) or q in str(u['name'])]
  211. page = self.paginate_queryset(elevation)
  212. if page is not None:
  213. rack_units = serializers.RackUnitSerializer(page, many=True, context={'request': request})
  214. return self.get_paginated_response(rack_units.data)
  215. #
  216. # Rack reservations
  217. #
  218. class RackReservationViewSet(NetBoxModelViewSet):
  219. queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
  220. serializer_class = serializers.RackReservationSerializer
  221. filterset_class = filtersets.RackReservationFilterSet
  222. #
  223. # Manufacturers
  224. #
  225. class ManufacturerViewSet(NetBoxModelViewSet):
  226. queryset = Manufacturer.objects.prefetch_related('tags').annotate(
  227. devicetype_count=count_related(DeviceType, 'manufacturer'),
  228. inventoryitem_count=count_related(InventoryItem, 'manufacturer'),
  229. platform_count=count_related(Platform, 'manufacturer')
  230. )
  231. serializer_class = serializers.ManufacturerSerializer
  232. filterset_class = filtersets.ManufacturerFilterSet
  233. #
  234. # Device/module types
  235. #
  236. class DeviceTypeViewSet(NetBoxModelViewSet):
  237. queryset = DeviceType.objects.prefetch_related('manufacturer', 'tags').annotate(
  238. device_count=count_related(Device, 'device_type')
  239. )
  240. serializer_class = serializers.DeviceTypeSerializer
  241. filterset_class = filtersets.DeviceTypeFilterSet
  242. brief_prefetch_fields = ['manufacturer']
  243. class ModuleTypeViewSet(NetBoxModelViewSet):
  244. queryset = ModuleType.objects.prefetch_related('manufacturer', 'tags').annotate(
  245. # module_count=count_related(Module, 'module_type')
  246. )
  247. serializer_class = serializers.ModuleTypeSerializer
  248. filterset_class = filtersets.ModuleTypeFilterSet
  249. brief_prefetch_fields = ['manufacturer']
  250. #
  251. # Device type components
  252. #
  253. class ConsolePortTemplateViewSet(NetBoxModelViewSet):
  254. queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
  255. serializer_class = serializers.ConsolePortTemplateSerializer
  256. filterset_class = filtersets.ConsolePortTemplateFilterSet
  257. class ConsoleServerPortTemplateViewSet(NetBoxModelViewSet):
  258. queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
  259. serializer_class = serializers.ConsoleServerPortTemplateSerializer
  260. filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
  261. class PowerPortTemplateViewSet(NetBoxModelViewSet):
  262. queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
  263. serializer_class = serializers.PowerPortTemplateSerializer
  264. filterset_class = filtersets.PowerPortTemplateFilterSet
  265. class PowerOutletTemplateViewSet(NetBoxModelViewSet):
  266. queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
  267. serializer_class = serializers.PowerOutletTemplateSerializer
  268. filterset_class = filtersets.PowerOutletTemplateFilterSet
  269. class InterfaceTemplateViewSet(NetBoxModelViewSet):
  270. queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
  271. serializer_class = serializers.InterfaceTemplateSerializer
  272. filterset_class = filtersets.InterfaceTemplateFilterSet
  273. class FrontPortTemplateViewSet(NetBoxModelViewSet):
  274. queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
  275. serializer_class = serializers.FrontPortTemplateSerializer
  276. filterset_class = filtersets.FrontPortTemplateFilterSet
  277. class RearPortTemplateViewSet(NetBoxModelViewSet):
  278. queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
  279. serializer_class = serializers.RearPortTemplateSerializer
  280. filterset_class = filtersets.RearPortTemplateFilterSet
  281. class ModuleBayTemplateViewSet(NetBoxModelViewSet):
  282. queryset = ModuleBayTemplate.objects.prefetch_related('device_type__manufacturer')
  283. serializer_class = serializers.ModuleBayTemplateSerializer
  284. filterset_class = filtersets.ModuleBayTemplateFilterSet
  285. class DeviceBayTemplateViewSet(NetBoxModelViewSet):
  286. queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
  287. serializer_class = serializers.DeviceBayTemplateSerializer
  288. filterset_class = filtersets.DeviceBayTemplateFilterSet
  289. class InventoryItemTemplateViewSet(NetBoxModelViewSet):
  290. queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
  291. serializer_class = serializers.InventoryItemTemplateSerializer
  292. filterset_class = filtersets.InventoryItemTemplateFilterSet
  293. #
  294. # Device roles
  295. #
  296. class DeviceRoleViewSet(NetBoxModelViewSet):
  297. queryset = DeviceRole.objects.prefetch_related('tags').annotate(
  298. device_count=count_related(Device, 'device_role'),
  299. virtualmachine_count=count_related(VirtualMachine, 'role')
  300. )
  301. serializer_class = serializers.DeviceRoleSerializer
  302. filterset_class = filtersets.DeviceRoleFilterSet
  303. #
  304. # Platforms
  305. #
  306. class PlatformViewSet(NetBoxModelViewSet):
  307. queryset = Platform.objects.prefetch_related('tags').annotate(
  308. device_count=count_related(Device, 'platform'),
  309. virtualmachine_count=count_related(VirtualMachine, 'platform')
  310. )
  311. serializer_class = serializers.PlatformSerializer
  312. filterset_class = filtersets.PlatformFilterSet
  313. #
  314. # Devices/modules
  315. #
  316. class DeviceViewSet(ConfigContextQuerySetMixin, NetBoxModelViewSet):
  317. queryset = Device.objects.prefetch_related(
  318. 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
  319. 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
  320. )
  321. filterset_class = filtersets.DeviceFilterSet
  322. pagination_class = StripCountAnnotationsPaginator
  323. def get_serializer_class(self):
  324. """
  325. Select the specific serializer based on the request context.
  326. If the `brief` query param equates to True, return the NestedDeviceSerializer
  327. If the `exclude` query param includes `config_context` as a value, return the DeviceSerializer
  328. Else, return the DeviceWithConfigContextSerializer
  329. """
  330. request = self.get_serializer_context()['request']
  331. if request.query_params.get('brief', False):
  332. return serializers.NestedDeviceSerializer
  333. elif 'config_context' in request.query_params.get('exclude', []):
  334. return serializers.DeviceSerializer
  335. return serializers.DeviceWithConfigContextSerializer
  336. @swagger_auto_schema(
  337. manual_parameters=[
  338. Parameter(
  339. name='method',
  340. in_='query',
  341. required=True,
  342. type=openapi.TYPE_STRING
  343. )
  344. ],
  345. responses={'200': serializers.DeviceNAPALMSerializer}
  346. )
  347. @action(detail=True, url_path='napalm')
  348. def napalm(self, request, pk):
  349. """
  350. Execute a NAPALM method on a Device
  351. """
  352. device = get_object_or_404(self.queryset, pk=pk)
  353. if not device.primary_ip:
  354. raise ServiceUnavailable("This device does not have a primary IP address configured.")
  355. if device.platform is None:
  356. raise ServiceUnavailable("No platform is configured for this device.")
  357. if not device.platform.napalm_driver:
  358. raise ServiceUnavailable(f"No NAPALM driver is configured for this device's platform: {device.platform}.")
  359. # Check for primary IP address from NetBox object
  360. if device.primary_ip:
  361. host = str(device.primary_ip.address.ip)
  362. else:
  363. # Raise exception for no IP address and no Name if device.name does not exist
  364. if not device.name:
  365. raise ServiceUnavailable(
  366. "This device does not have a primary IP address or device name to lookup configured."
  367. )
  368. try:
  369. # Attempt to complete a DNS name resolution if no primary_ip is set
  370. host = socket.gethostbyname(device.name)
  371. except socket.gaierror:
  372. # Name lookup failure
  373. raise ServiceUnavailable(
  374. f"Name lookup failure, unable to resolve IP address for {device.name}. Please set Primary IP or "
  375. f"setup name resolution.")
  376. # Check that NAPALM is installed
  377. try:
  378. import napalm
  379. from napalm.base.exceptions import ModuleImportError
  380. except ModuleNotFoundError as e:
  381. if getattr(e, 'name') == 'napalm':
  382. raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.")
  383. raise e
  384. # Validate the configured driver
  385. try:
  386. driver = napalm.get_network_driver(device.platform.napalm_driver)
  387. except ModuleImportError:
  388. raise ServiceUnavailable("NAPALM driver for platform {} not found: {}.".format(
  389. device.platform, device.platform.napalm_driver
  390. ))
  391. # Verify user permission
  392. if not request.user.has_perm('dcim.napalm_read_device'):
  393. return HttpResponseForbidden()
  394. napalm_methods = request.GET.getlist('method')
  395. response = OrderedDict([(m, None) for m in napalm_methods])
  396. config = get_config()
  397. username = config.NAPALM_USERNAME
  398. password = config.NAPALM_PASSWORD
  399. timeout = config.NAPALM_TIMEOUT
  400. optional_args = config.NAPALM_ARGS.copy()
  401. if device.platform.napalm_args is not None:
  402. optional_args.update(device.platform.napalm_args)
  403. # Update NAPALM parameters according to the request headers
  404. for header in request.headers:
  405. if header[:9].lower() != 'x-napalm-':
  406. continue
  407. key = header[9:]
  408. if key.lower() == 'username':
  409. username = request.headers[header]
  410. elif key.lower() == 'password':
  411. password = request.headers[header]
  412. elif key:
  413. optional_args[key.lower()] = request.headers[header]
  414. # Connect to the device
  415. d = driver(
  416. hostname=host,
  417. username=username,
  418. password=password,
  419. timeout=timeout,
  420. optional_args=optional_args
  421. )
  422. try:
  423. d.open()
  424. except Exception as e:
  425. raise ServiceUnavailable("Error connecting to the device at {}: {}".format(host, e))
  426. # Validate and execute each specified NAPALM method
  427. for method in napalm_methods:
  428. if not hasattr(driver, method):
  429. response[method] = {'error': 'Unknown NAPALM method'}
  430. continue
  431. if not method.startswith('get_'):
  432. response[method] = {'error': 'Only get_* NAPALM methods are supported'}
  433. continue
  434. try:
  435. response[method] = getattr(d, method)()
  436. except NotImplementedError:
  437. response[method] = {'error': 'Method {} not implemented for NAPALM driver {}'.format(method, driver)}
  438. except Exception as e:
  439. response[method] = {'error': 'Method {} failed: {}'.format(method, e)}
  440. d.close()
  441. return Response(response)
  442. class ModuleViewSet(NetBoxModelViewSet):
  443. queryset = Module.objects.prefetch_related(
  444. 'device', 'module_bay', 'module_type__manufacturer', 'tags',
  445. )
  446. serializer_class = serializers.ModuleSerializer
  447. filterset_class = filtersets.ModuleFilterSet
  448. #
  449. # Device components
  450. #
  451. class ConsolePortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  452. queryset = ConsolePort.objects.prefetch_related(
  453. 'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
  454. )
  455. serializer_class = serializers.ConsolePortSerializer
  456. filterset_class = filtersets.ConsolePortFilterSet
  457. brief_prefetch_fields = ['device']
  458. class ConsoleServerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  459. queryset = ConsoleServerPort.objects.prefetch_related(
  460. 'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
  461. )
  462. serializer_class = serializers.ConsoleServerPortSerializer
  463. filterset_class = filtersets.ConsoleServerPortFilterSet
  464. brief_prefetch_fields = ['device']
  465. class PowerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
  466. queryset = PowerPort.objects.prefetch_related(
  467. 'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
  468. )
  469. serializer_class = serializers.PowerPortSerializer
  470. filterset_class = filtersets.PowerPortFilterSet
  471. brief_prefetch_fields = ['device']
  472. class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet):
  473. queryset = PowerOutlet.objects.prefetch_related(
  474. 'device', 'module__module_bay', '_path__destination', 'cable', '_link_peer', 'tags'
  475. )
  476. serializer_class = serializers.PowerOutletSerializer
  477. filterset_class = filtersets.PowerOutletFilterSet
  478. brief_prefetch_fields = ['device']
  479. class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet):
  480. queryset = Interface.objects.prefetch_related(
  481. 'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path__destination', 'cable', '_link_peer',
  482. 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags'
  483. )
  484. serializer_class = serializers.InterfaceSerializer
  485. filterset_class = filtersets.InterfaceFilterSet
  486. brief_prefetch_fields = ['device']
  487. class FrontPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
  488. queryset = FrontPort.objects.prefetch_related(
  489. 'device__device_type__manufacturer', 'module__module_bay', 'rear_port', 'cable', 'tags'
  490. )
  491. serializer_class = serializers.FrontPortSerializer
  492. filterset_class = filtersets.FrontPortFilterSet
  493. brief_prefetch_fields = ['device']
  494. class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
  495. queryset = RearPort.objects.prefetch_related(
  496. 'device__device_type__manufacturer', 'module__module_bay', 'cable', 'tags'
  497. )
  498. serializer_class = serializers.RearPortSerializer
  499. filterset_class = filtersets.RearPortFilterSet
  500. brief_prefetch_fields = ['device']
  501. class ModuleBayViewSet(NetBoxModelViewSet):
  502. queryset = ModuleBay.objects.prefetch_related('tags', 'installed_module')
  503. serializer_class = serializers.ModuleBaySerializer
  504. filterset_class = filtersets.ModuleBayFilterSet
  505. brief_prefetch_fields = ['device']
  506. class DeviceBayViewSet(NetBoxModelViewSet):
  507. queryset = DeviceBay.objects.prefetch_related('installed_device', 'tags')
  508. serializer_class = serializers.DeviceBaySerializer
  509. filterset_class = filtersets.DeviceBayFilterSet
  510. brief_prefetch_fields = ['device']
  511. class InventoryItemViewSet(NetBoxModelViewSet):
  512. queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags')
  513. serializer_class = serializers.InventoryItemSerializer
  514. filterset_class = filtersets.InventoryItemFilterSet
  515. brief_prefetch_fields = ['device']
  516. #
  517. # Device component roles
  518. #
  519. class InventoryItemRoleViewSet(NetBoxModelViewSet):
  520. queryset = InventoryItemRole.objects.prefetch_related('tags').annotate(
  521. inventoryitem_count=count_related(InventoryItem, 'role')
  522. )
  523. serializer_class = serializers.InventoryItemRoleSerializer
  524. filterset_class = filtersets.InventoryItemRoleFilterSet
  525. #
  526. # Cables
  527. #
  528. class CableViewSet(NetBoxModelViewSet):
  529. metadata_class = ContentTypeMetadata
  530. queryset = Cable.objects.prefetch_related(
  531. 'termination_a', 'termination_b'
  532. )
  533. serializer_class = serializers.CableSerializer
  534. filterset_class = filtersets.CableFilterSet
  535. #
  536. # Virtual chassis
  537. #
  538. class VirtualChassisViewSet(NetBoxModelViewSet):
  539. queryset = VirtualChassis.objects.prefetch_related('tags').annotate(
  540. member_count=count_related(Device, 'virtual_chassis')
  541. )
  542. serializer_class = serializers.VirtualChassisSerializer
  543. filterset_class = filtersets.VirtualChassisFilterSet
  544. brief_prefetch_fields = ['master']
  545. #
  546. # Power panels
  547. #
  548. class PowerPanelViewSet(NetBoxModelViewSet):
  549. queryset = PowerPanel.objects.prefetch_related(
  550. 'site', 'location'
  551. ).annotate(
  552. powerfeed_count=count_related(PowerFeed, 'power_panel')
  553. )
  554. serializer_class = serializers.PowerPanelSerializer
  555. filterset_class = filtersets.PowerPanelFilterSet
  556. #
  557. # Power feeds
  558. #
  559. class PowerFeedViewSet(PathEndpointMixin, NetBoxModelViewSet):
  560. queryset = PowerFeed.objects.prefetch_related(
  561. 'power_panel', 'rack', '_path__destination', 'cable', '_link_peer', 'tags'
  562. )
  563. serializer_class = serializers.PowerFeedSerializer
  564. filterset_class = filtersets.PowerFeedFilterSet
  565. #
  566. # Miscellaneous
  567. #
  568. class ConnectedDeviceViewSet(ViewSet):
  569. """
  570. This endpoint allows a user to determine what device (if any) is connected to a given peer device and peer
  571. interface. This is useful in a situation where a device boots with no configuration, but can detect its neighbors
  572. via a protocol such as LLDP. Two query parameters must be included in the request:
  573. * `peer_device`: The name of the peer device
  574. * `peer_interface`: The name of the peer interface
  575. """
  576. permission_classes = [IsAuthenticatedOrLoginNotRequired]
  577. _device_param = Parameter(
  578. name='peer_device',
  579. in_='query',
  580. description='The name of the peer device',
  581. required=True,
  582. type=openapi.TYPE_STRING
  583. )
  584. _interface_param = Parameter(
  585. name='peer_interface',
  586. in_='query',
  587. description='The name of the peer interface',
  588. required=True,
  589. type=openapi.TYPE_STRING
  590. )
  591. def get_view_name(self):
  592. return "Connected Device Locator"
  593. @swagger_auto_schema(
  594. manual_parameters=[_device_param, _interface_param],
  595. responses={'200': serializers.DeviceSerializer}
  596. )
  597. def list(self, request):
  598. peer_device_name = request.query_params.get(self._device_param.name)
  599. peer_interface_name = request.query_params.get(self._interface_param.name)
  600. if not peer_device_name or not peer_interface_name:
  601. raise MissingFilterException(detail='Request must include "peer_device" and "peer_interface" filters.')
  602. # Determine local endpoint from peer interface's connection
  603. peer_device = get_object_or_404(
  604. Device.objects.restrict(request.user, 'view'),
  605. name=peer_device_name
  606. )
  607. peer_interface = get_object_or_404(
  608. Interface.objects.restrict(request.user, 'view'),
  609. device=peer_device,
  610. name=peer_interface_name
  611. )
  612. endpoint = peer_interface.connected_endpoint
  613. # If an Interface, return the parent device
  614. if type(endpoint) is Interface:
  615. device = get_object_or_404(
  616. Device.objects.restrict(request.user, 'view'),
  617. pk=endpoint.device_id
  618. )
  619. return Response(serializers.DeviceSerializer(device, context={'request': request}).data)
  620. # Connected endpoint is none or not an Interface
  621. raise Http404