device_components.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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. status = ChoiceField(choices=PowerOutletStatusChoices, required=False)
  143. class Meta:
  144. model = PowerOutlet
  145. fields = [
  146. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'status', 'color',
  147. 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers',
  148. 'link_peers_type', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable',
  149. 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  150. ]
  151. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  152. class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
  153. device = DeviceSerializer(nested=True)
  154. vdcs = SerializedPKRelatedField(
  155. queryset=VirtualDeviceContext.objects.all(),
  156. serializer=VirtualDeviceContextSerializer,
  157. nested=True,
  158. required=False,
  159. many=True
  160. )
  161. module = ModuleSerializer(
  162. nested=True,
  163. fields=('id', 'url', 'display', 'device', 'module_bay'),
  164. required=False,
  165. allow_null=True
  166. )
  167. type = ChoiceField(choices=InterfaceTypeChoices)
  168. parent = NestedInterfaceSerializer(required=False, allow_null=True)
  169. bridge = NestedInterfaceSerializer(required=False, allow_null=True)
  170. lag = NestedInterfaceSerializer(required=False, allow_null=True)
  171. mode = ChoiceField(choices=InterfaceModeChoices, required=False, allow_blank=True)
  172. duplex = ChoiceField(choices=InterfaceDuplexChoices, required=False, allow_blank=True, allow_null=True)
  173. rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_blank=True)
  174. rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False, allow_blank=True)
  175. poe_mode = ChoiceField(choices=InterfacePoEModeChoices, required=False, allow_blank=True)
  176. poe_type = ChoiceField(choices=InterfacePoETypeChoices, required=False, allow_blank=True)
  177. untagged_vlan = VLANSerializer(nested=True, required=False, allow_null=True)
  178. tagged_vlans = SerializedPKRelatedField(
  179. queryset=VLAN.objects.all(),
  180. serializer=VLANSerializer,
  181. nested=True,
  182. required=False,
  183. many=True
  184. )
  185. qinq_svlan = VLANSerializer(nested=True, required=False, allow_null=True)
  186. vlan_translation_policy = VLANTranslationPolicySerializer(nested=True, required=False, allow_null=True)
  187. vrf = VRFSerializer(nested=True, required=False, allow_null=True)
  188. l2vpn_termination = L2VPNTerminationSerializer(nested=True, read_only=True, allow_null=True)
  189. wireless_link = NestedWirelessLinkSerializer(read_only=True, allow_null=True)
  190. wireless_lans = SerializedPKRelatedField(
  191. queryset=WirelessLAN.objects.all(),
  192. serializer=WirelessLANSerializer,
  193. nested=True,
  194. required=False,
  195. many=True
  196. )
  197. count_ipaddresses = serializers.IntegerField(read_only=True)
  198. count_fhrp_groups = serializers.IntegerField(read_only=True)
  199. # Maintains backward compatibility with NetBox <v4.2
  200. mac_address = serializers.CharField(allow_null=True, read_only=True)
  201. primary_mac_address = MACAddressSerializer(nested=True, required=False, allow_null=True)
  202. mac_addresses = MACAddressSerializer(many=True, nested=True, read_only=True, allow_null=True)
  203. wwn = serializers.CharField(required=False, default=None, allow_blank=True, allow_null=True)
  204. class Meta:
  205. model = Interface
  206. fields = [
  207. 'id', 'url', 'display_url', 'display', 'device', 'vdcs', 'module', 'name', 'label', 'type', 'enabled',
  208. 'parent', 'bridge', 'lag', 'mtu', 'mac_address', 'primary_mac_address', 'mac_addresses', 'speed', 'duplex',
  209. 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'poe_mode', 'poe_type',
  210. 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan',
  211. 'vlan_translation_policy', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers',
  212. 'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints',
  213. 'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
  214. 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
  215. ]
  216. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  217. def validate(self, data):
  218. if not self.nested:
  219. # Validate 802.1q mode and vlan(s)
  220. mode = None
  221. tagged_vlans = []
  222. # Gather Information
  223. if self.instance:
  224. mode = data.get('mode') if 'mode' in data.keys() else self.instance.mode
  225. untagged_vlan = data.get('untagged_vlan') if 'untagged_vlan' in data.keys() else \
  226. self.instance.untagged_vlan
  227. qinq_svlan = data.get('qinq_svlan') if 'qinq_svlan' in data.keys() else \
  228. self.instance.qinq_svlan
  229. tagged_vlans = data.get('tagged_vlans') if 'tagged_vlans' in data.keys() else \
  230. self.instance.tagged_vlans.all()
  231. else:
  232. mode = data.get('mode', None)
  233. untagged_vlan = data.get('untagged_vlan') if 'untagged_vlan' in data.keys() else None
  234. qinq_svlan = data.get('qinq_svlan') if 'qinq_svlan' in data.keys() else None
  235. tagged_vlans = data.get('tagged_vlans') if 'tagged_vlans' in data.keys() else None
  236. errors = {}
  237. # Non Q-in-Q mode with service vlan set
  238. if mode != InterfaceModeChoices.MODE_Q_IN_Q and qinq_svlan:
  239. errors.update({
  240. 'qinq_svlan': _("Interface mode does not support q-in-q service vlan")
  241. })
  242. # Routed mode
  243. if not mode:
  244. # Untagged vlan
  245. if untagged_vlan:
  246. errors.update({
  247. 'untagged_vlan': _("Interface mode does not support untagged vlan")
  248. })
  249. # Tagged vlan
  250. if tagged_vlans:
  251. errors.update({
  252. 'tagged_vlans': _("Interface mode does not support tagged vlans")
  253. })
  254. # Non-tagged mode
  255. elif mode in (InterfaceModeChoices.MODE_TAGGED_ALL, InterfaceModeChoices.MODE_ACCESS) and tagged_vlans:
  256. errors.update({
  257. 'tagged_vlans': _("Interface mode does not support tagged vlans")
  258. })
  259. if errors:
  260. raise serializers.ValidationError(errors)
  261. # Validate many-to-many VLAN assignments
  262. device = self.instance.device if self.instance else data.get('device')
  263. for vlan in data.get('tagged_vlans', []):
  264. if vlan.site not in [device.site, None]:
  265. raise serializers.ValidationError({
  266. 'tagged_vlans': f"VLAN {vlan} must belong to the same site as the interface's parent device, "
  267. f"or it must be global."
  268. })
  269. return super().validate(data)
  270. class RearPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
  271. device = DeviceSerializer(nested=True)
  272. module = ModuleSerializer(
  273. nested=True,
  274. fields=('id', 'url', 'display', 'device', 'module_bay'),
  275. required=False,
  276. allow_null=True
  277. )
  278. type = ChoiceField(choices=PortTypeChoices)
  279. class Meta:
  280. model = RearPort
  281. fields = [
  282. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'positions',
  283. 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'tags',
  284. 'custom_fields', 'created', 'last_updated', '_occupied',
  285. ]
  286. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  287. class FrontPortRearPortSerializer(WritableNestedSerializer):
  288. """
  289. NestedRearPortSerializer but with parent device omitted (since front and rear ports must belong to same device)
  290. """
  291. class Meta:
  292. model = RearPort
  293. fields = ['id', 'url', 'display_url', 'display', 'name', 'label', 'description']
  294. class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer):
  295. device = DeviceSerializer(nested=True)
  296. module = ModuleSerializer(
  297. nested=True,
  298. fields=('id', 'url', 'display', 'device', 'module_bay'),
  299. required=False,
  300. allow_null=True
  301. )
  302. type = ChoiceField(choices=PortTypeChoices)
  303. rear_port = FrontPortRearPortSerializer()
  304. class Meta:
  305. model = FrontPort
  306. fields = [
  307. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port',
  308. 'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_end', 'link_peers',
  309. 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
  310. ]
  311. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
  312. class ModuleBaySerializer(NetBoxModelSerializer):
  313. device = DeviceSerializer(nested=True)
  314. module = ModuleSerializer(
  315. nested=True,
  316. fields=('id', 'url', 'display'),
  317. required=False,
  318. allow_null=True,
  319. default=None
  320. )
  321. installed_module = ModuleSerializer(
  322. nested=True,
  323. fields=('id', 'url', 'display', 'serial', 'description'),
  324. required=False,
  325. allow_null=True
  326. )
  327. class Meta:
  328. model = ModuleBay
  329. fields = [
  330. 'id', 'url', 'display_url', 'display', 'device', 'module', 'name', 'installed_module', 'label', 'position',
  331. 'description', 'tags', 'custom_fields', 'created', 'last_updated',
  332. ]
  333. brief_fields = ('id', 'url', 'display', 'installed_module', 'name', 'description')
  334. class DeviceBaySerializer(NetBoxModelSerializer):
  335. device = DeviceSerializer(nested=True)
  336. installed_device = DeviceSerializer(nested=True, required=False, allow_null=True)
  337. class Meta:
  338. model = DeviceBay
  339. fields = [
  340. 'id', 'url', 'display_url', 'display', 'device', 'name', 'label', 'description', 'installed_device',
  341. 'tags', 'custom_fields', 'created', 'last_updated',
  342. ]
  343. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description')
  344. class InventoryItemSerializer(NetBoxModelSerializer):
  345. device = DeviceSerializer(nested=True)
  346. parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
  347. role = InventoryItemRoleSerializer(nested=True, required=False, allow_null=True)
  348. manufacturer = ManufacturerSerializer(nested=True, required=False, allow_null=True, default=None)
  349. component_type = ContentTypeField(
  350. queryset=ContentType.objects.filter(MODULAR_COMPONENT_MODELS),
  351. required=False,
  352. allow_null=True
  353. )
  354. component = serializers.SerializerMethodField(read_only=True, allow_null=True)
  355. _depth = serializers.IntegerField(source='level', read_only=True)
  356. status = ChoiceField(choices=InventoryItemStatusChoices, required=False)
  357. class Meta:
  358. model = InventoryItem
  359. fields = [
  360. 'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role',
  361. 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type',
  362. 'component_id', 'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
  363. ]
  364. brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', '_depth')
  365. @extend_schema_field(serializers.JSONField(allow_null=True))
  366. def get_component(self, obj):
  367. if obj.component is None:
  368. return None
  369. serializer = get_serializer_for_model(obj.component)
  370. context = {'request': self.context['request']}
  371. return serializer(obj.component, nested=True, context=context).data