device_components.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. from django.utils.translation import gettext as _
  2. from django.contrib.contenttypes.models import ContentType
  3. from drf_spectacular.utils import extend_schema_field
  4. from rest_framework import serializers
  5. from dcim.choices import *
  6. from dcim.constants import *
  7. from dcim.models import (
  8. ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
  9. RearPort, VirtualDeviceContext,
  10. )
  11. from ipam.api.serializers_.vlans import VLANSerializer, VLANTranslationPolicySerializer
  12. from ipam.api.serializers_.vrfs import VRFSerializer
  13. from ipam.models import VLAN
  14. from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
  15. from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
  16. from utilities.api import get_serializer_for_model
  17. from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
  18. from wireless.api.serializers_.nested import NestedWirelessLinkSerializer
  19. from wireless.api.serializers_.wirelesslans import WirelessLANSerializer
  20. from wireless.choices import *
  21. from wireless.models import WirelessLAN
  22. from .base import ConnectedEndpointsSerializer
  23. from .cables import CabledObjectSerializer
  24. from .devices import DeviceSerializer, MACAddressSerializer, ModuleSerializer, VirtualDeviceContextSerializer
  25. from .manufacturers import ManufacturerSerializer
  26. from .nested import NestedInterfaceSerializer
  27. from .roles import InventoryItemRoleSerializer
  28. __all__ = (
  29. 'ConsolePortSerializer',
  30. 'ConsoleServerPortSerializer',
  31. 'DeviceBaySerializer',
  32. 'FrontPortSerializer',
  33. 'InterfaceSerializer',
  34. 'InventoryItemSerializer',
  35. 'ModuleBaySerializer',
  36. 'PowerOutletSerializer',
  37. 'PowerPortSerializer',
  38. 'RearPortSerializer',
  39. )
  40. class ConsoleServerPortSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
  41. device = DeviceSerializer(nested=True)
  42. module = ModuleSerializer(
  43. nested=True,
  44. fields=('id', 'url', 'display', 'device', 'module_bay'),
  45. required=False,
  46. allow_null=True
  47. )
  48. type = ChoiceField(
  49. choices=ConsolePortTypeChoices,
  50. allow_blank=True,
  51. required=False
  52. )
  53. speed = ChoiceField(
  54. choices=ConsolePortSpeedChoices,
  55. allow_null=True,
  56. required=False
  57. )
  58. class Meta:
  59. model = ConsoleServerPort
  60. fields = [
  61. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
  62. 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints',
  63. 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
  64. 'last_updated', '_occupied',
  65. ]
  66. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  67. class ConsolePortSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
  68. device = DeviceSerializer(nested=True)
  69. module = ModuleSerializer(
  70. nested=True,
  71. fields=('id', 'url', 'display', 'device', 'module_bay'),
  72. required=False,
  73. allow_null=True
  74. )
  75. type = ChoiceField(
  76. choices=ConsolePortTypeChoices,
  77. allow_blank=True,
  78. required=False
  79. )
  80. speed = ChoiceField(
  81. choices=ConsolePortSpeedChoices,
  82. allow_null=True,
  83. required=False
  84. )
  85. class Meta:
  86. model = ConsolePort
  87. fields = [
  88. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'speed', 'description',
  89. 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'connected_endpoints',
  90. 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
  91. 'last_updated', '_occupied',
  92. ]
  93. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  94. class PowerPortSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
  95. device = DeviceSerializer(nested=True)
  96. module = ModuleSerializer(
  97. nested=True,
  98. fields=('id', 'url', 'display', 'device', 'module_bay'),
  99. required=False,
  100. allow_null=True
  101. )
  102. type = ChoiceField(
  103. choices=PowerPortTypeChoices,
  104. allow_blank=True,
  105. required=False,
  106. allow_null=True
  107. )
  108. class Meta:
  109. model = PowerPort
  110. fields = [
  111. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'maximum_draw',
  112. 'allocated_draw', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
  113. 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields',
  114. 'created', 'last_updated', '_occupied',
  115. ]
  116. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  117. class PowerOutletSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
  118. device = DeviceSerializer(nested=True)
  119. module = ModuleSerializer(
  120. nested=True,
  121. fields=('id', 'url', 'display', 'device', 'module_bay'),
  122. required=False,
  123. allow_null=True
  124. )
  125. type = ChoiceField(
  126. choices=PowerOutletTypeChoices,
  127. allow_blank=True,
  128. required=False,
  129. allow_null=True
  130. )
  131. power_port = PowerPortSerializer(
  132. nested=True,
  133. required=False,
  134. allow_null=True
  135. )
  136. feed_leg = ChoiceField(
  137. choices=PowerOutletFeedLegChoices,
  138. allow_blank=True,
  139. required=False,
  140. allow_null=True
  141. )
  142. class Meta:
  143. model = PowerOutlet
  144. fields = [
  145. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'power_port',
  146. 'feed_leg', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
  147. 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields',
  148. 'created', 'last_updated', '_occupied',
  149. ]
  150. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  151. class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
  152. device = DeviceSerializer(nested=True)
  153. vdcs = SerializedPKRelatedField(
  154. queryset=VirtualDeviceContext.objects.all(),
  155. serializer=VirtualDeviceContextSerializer,
  156. nested=True,
  157. required=False,
  158. many=True
  159. )
  160. module = ModuleSerializer(
  161. nested=True,
  162. fields=('id', 'url', 'display', 'device', 'module_bay'),
  163. required=False,
  164. allow_null=True
  165. )
  166. type = ChoiceField(choices=InterfaceTypeChoices)
  167. parent = NestedInterfaceSerializer(required=False, allow_null=True)
  168. bridge = NestedInterfaceSerializer(required=False, allow_null=True)
  169. lag = NestedInterfaceSerializer(required=False, allow_null=True)
  170. mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_blank=True)
  171. duplex = ChoiceField(choices=InterfaceDuplexChoices, required=False, allow_blank=True, allow_null=True)
  172. rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_blank=True)
  173. rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False, allow_blank=True)
  174. poe_mode = ChoiceField(choices=InterfacePoEModeChoices, required=False, allow_blank=True)
  175. poe_type = ChoiceField(choices=InterfacePoETypeChoices, required=False, allow_blank=True)
  176. untagged_vlan = VLANSerializer(nested=True, required=False, allow_null=True)
  177. tagged_vlans = SerializedPKRelatedField(
  178. queryset=VLAN.objects.all(),
  179. serializer=VLANSerializer,
  180. nested=True,
  181. required=False,
  182. many=True
  183. )
  184. qinq_svlan = VLANSerializer(nested=True, required=False, allow_null=True)
  185. vlan_translation_policy = VLANTranslationPolicySerializer(nested=True, required=False, allow_null=True)
  186. vrf = VRFSerializer(nested=True, required=False, allow_null=True)
  187. l2vpn_termination = L2VPNTerminationSerializer(nested=True, read_only=True, allow_null=True)
  188. wireless_link = NestedWirelessLinkSerializer(read_only=True, allow_null=True)
  189. wireless_lans = SerializedPKRelatedField(
  190. queryset=WirelessLAN.objects.all(),
  191. serializer=WirelessLANSerializer,
  192. nested=True,
  193. required=False,
  194. many=True
  195. )
  196. count_ipaddresses = serializers.IntegerField(read_only=True)
  197. count_fhrp_groups = serializers.IntegerField(read_only=True)
  198. # Maintains backward compatibility with NetBox <v4.2
  199. mac_address = serializers.CharField(allow_null=True, read_only=True)
  200. primary_mac_address = MACAddressSerializer(nested=True, required=False, allow_null=True)
  201. mac_addresses = MACAddressSerializer(many=True, nested=True, read_only=True, allow_null=True)
  202. wwn = serializers.CharField(required=False, default=None, allow_blank=True, allow_null=True)
  203. class Meta:
  204. model = Interface
  205. fields = [
  206. 'id', 'url', 'display_url', 'display', 'device', 'vdcs', 'module', 'name', 'label', 'type', 'enabled',
  207. 'parent', 'bridge', 'lag', 'mtu', 'mac_address', 'primary_mac_address', 'mac_addresses', 'speed', 'duplex',
  208. 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'poe_mode', 'poe_type',
  209. 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan',
  210. 'vlan_translation_policy', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers',
  211. 'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints',
  212. 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
  213. 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
  214. ]
  215. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  216. def validate(self, data):
  217. if not self.nested:
  218. # Validate 802.1q mode and vlan(s)
  219. mode = None
  220. tagged_vlans = []
  221. # Gather Information
  222. if self.instance:
  223. mode = data.get('mode') if 'mode' in data.keys() else self.instance.mode
  224. untagged_vlan = data.get('untagged_vlan') if 'untagged_vlan' in data.keys() else \
  225. self.instance.untagged_vlan
  226. qinq_svlan = data.get('qinq_svlan') if 'qinq_svlan' in data.keys() else \
  227. self.instance.qinq_svlan
  228. tagged_vlans = data.get('tagged_vlans') if 'tagged_vlans' in data.keys() else \
  229. self.instance.tagged_vlans.all()
  230. else:
  231. mode = data.get('mode', None)
  232. untagged_vlan = data.get('untagged_vlan') if 'untagged_vlan' in data.keys() else None
  233. qinq_svlan = data.get('qinq_svlan') if 'qinq_svlan' in data.keys() else None
  234. tagged_vlans = data.get('tagged_vlans') if 'tagged_vlans' in data.keys() else None
  235. errors = {}
  236. # Non Q-in-Q mode with service vlan set
  237. if mode != InterfaceModeChoices.MODE_Q_IN_Q and qinq_svlan:
  238. errors.update({
  239. 'qinq_svlan': _("Interface mode does not support q-in-q service vlan")
  240. })
  241. # Routed mode
  242. if not mode:
  243. # Untagged vlan
  244. if untagged_vlan:
  245. errors.update({
  246. 'untagged_vlan': _("Interface mode does not support untagged vlan")
  247. })
  248. # Tagged vlan
  249. if tagged_vlans:
  250. errors.update({
  251. 'tagged_vlans': _("Interface mode does not support tagged vlans")
  252. })
  253. # Non-tagged mode
  254. elif mode in (InterfaceModeChoices.MODE_TAGGED_ALL, InterfaceModeChoices.MODE_ACCESS) and tagged_vlans:
  255. errors.update({
  256. 'tagged_vlans': _("Interface mode does not support tagged vlans")
  257. })
  258. if errors:
  259. raise serializers.ValidationError(errors)
  260. # Validate many-to-many VLAN assignments
  261. device = self.instance.device if self.instance else data.get('device')
  262. for vlan in data.get('tagged_vlans', []):
  263. if vlan.site not in [device.site, None]:
  264. raise serializers.ValidationError({
  265. 'tagged_vlans': f"VLAN {vlan} must belong to the same site as the interface's parent device, "
  266. f"or it must be global."
  267. })
  268. return super().validate(data)
  269. class RearPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
  270. device = DeviceSerializer(nested=True)
  271. module = ModuleSerializer(
  272. nested=True,
  273. fields=('id', 'url', 'display', 'device', 'module_bay'),
  274. required=False,
  275. allow_null=True
  276. )
  277. type = ChoiceField(choices=PortTypeChoices)
  278. class Meta:
  279. model = RearPort
  280. fields = [
  281. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
  282. 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'tags',
  283. 'custom_fields', 'created', 'last_updated', '_occupied',
  284. ]
  285. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  286. class FrontPortRearPortSerializer(WritableNestedSerializer):
  287. """
  288. NestedRearPortSerializer but with parent device omitted (since front and rear ports must belong to same device)
  289. """
  290. class Meta:
  291. model = RearPort
  292. fields = ['id', 'url', 'display_url', 'display', 'name', 'label', 'description']
  293. class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
  294. device = DeviceSerializer(nested=True)
  295. module = ModuleSerializer(
  296. nested=True,
  297. fields=('id', 'url', 'display', 'device', 'module_bay'),
  298. required=False,
  299. allow_null=True
  300. )
  301. type = ChoiceField(choices=PortTypeChoices)
  302. rear_port = FrontPortRearPortSerializer()
  303. class Meta:
  304. model = FrontPort
  305. fields = [
  306. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port',
  307. 'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers',
  308. 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  309. ]
  310. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  311. class ModuleBaySerializer(NetBoxModelSerializer):
  312. device = DeviceSerializer(nested=True)
  313. module = ModuleSerializer(
  314. nested=True,
  315. fields=('id', 'url', 'display'),
  316. required=False,
  317. allow_null=True,
  318. default=None
  319. )
  320. installed_module = ModuleSerializer(
  321. nested=True,
  322. fields=('id', 'url', 'display', 'serial', 'description'),
  323. required=False,
  324. allow_null=True
  325. )
  326. class Meta:
  327. model = ModuleBay
  328. fields = [
  329. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'installed_module', 'label', 'position',
  330. 'description', 'tags', 'custom_fields', 'created', 'last_updated',
  331. ]
  332. brief_fields = ('id', 'url', 'display', 'installed_module', 'name', 'description')
  333. class DeviceBaySerializer(NetBoxModelSerializer):
  334. device = DeviceSerializer(nested=True)
  335. installed_device = DeviceSerializer(nested=True, required=False, allow_null=True)
  336. class Meta:
  337. model = DeviceBay
  338. fields = [
  339. 'id', 'url', 'display_url', 'display', 'device', 'name', 'label', 'description', 'installed_device',
  340. 'tags', 'custom_fields', 'created', 'last_updated',
  341. ]
  342. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description')
  343. class InventoryItemSerializer(NetBoxModelSerializer):
  344. device = DeviceSerializer(nested=True)
  345. parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
  346. role = InventoryItemRoleSerializer(nested=True, required=False, allow_null=True)
  347. manufacturer = ManufacturerSerializer(nested=True, required=False, allow_null=True, default=None)
  348. component_type = ContentTypeField(
  349. queryset=ContentType.objects.filter(MODULAR_COMPONENT_MODELS),
  350. required=False,
  351. allow_null=True
  352. )
  353. component = serializers.SerializerMethodField(read_only=True, allow_null=True)
  354. _depth = serializers.IntegerField(source='level', read_only=True)
  355. status = ChoiceField(choices=InventoryItemStatusChoices, required=False)
  356. class Meta:
  357. model = InventoryItem
  358. fields = [
  359. 'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role',
  360. 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type',
  361. 'component_id', 'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
  362. ]
  363. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', '_depth')
  364. @extend_schema_field(serializers.JSONField(allow_null=True))
  365. def get_component(self, obj):
  366. if obj.component is None:
  367. return None
  368. serializer = get_serializer_for_model(obj.component)
  369. context = {'request': self.context['request']}
  370. return serializer(obj.component, nested=True, context=context).data