views.py 26 KB

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