From 02ffa2e460938039c39e3a435c98acb9776d6251 Mon Sep 17 00:00:00 2001 From: IluaAir Date: Wed, 4 Feb 2026 00:39:18 +0300 Subject: [PATCH] init --- .gitignore | 1 + check_vchassis_stack_ports.py | 77 +++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 .gitignore create mode 100644 check_vchassis_stack_ports.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..757fee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea \ No newline at end of file diff --git a/check_vchassis_stack_ports.py b/check_vchassis_stack_ports.py new file mode 100644 index 0000000..9b4ffcd --- /dev/null +++ b/check_vchassis_stack_ports.py @@ -0,0 +1,77 @@ +import re + +from dcim.models.devices import Device +from extras.models.tags import Tag +from extras.scripts import ObjectVar, Script +from dcim.models import VirtualChassis, Interface +from netbox.choices import ColorChoices +from utilities.exceptions import AbortScript + + +class Vchassis_checker(Script): + TAGS = 'sync:vchas_interface' + IFACE_RE = re.compile(r'^(?P[A-Za-z]+)\s?(?:(?P\d+)/)?(?P\d+/\d+)$') + + class Meta(Script.Meta): + commit_default = False + name = 'Переопределение интерфейсов' + description = 'Переопределение интерфейсов у стекированных коммутаторов' + + virtual_chassis = ObjectVar( + model=VirtualChassis, + query_params={'tags': None}, + ) + + def run(self, data, commit): + if Tag.objects.filter(name=self.TAGS).exists(): + tag = Tag.objects.get(name=self.TAGS) + else: + self.log_warning(f'Отсутствует tag {self.TAGS}') + tag_data = { + 'color': ColorChoices.COLOR_LIGHT_BLUE, + 'name': self.TAGS, + 'slug': self.TAGS.replace(':', '_'), + 'description': 'тег для автоматического назначения на синхронизированные интерфейсы vchassis', + } + tag = Tag(**tag_data) + if commit: + tag.save() + self.log_success(f'Создан тег: {self.TAGS}') + vc = data['virtual_chassis'] + devices: list[Device] = list(vc.members.all().order_by('vc_position')) + if not vc._state.adding and vc.master and vc.master not in devices: + raise AbortScript(f'В стеке {vc.name} отсутствует мастер') + for device in devices: + interfaces = Interface.objects.filter(device=device) + for iface in interfaces: + m = self.IFACE_RE.match(iface) + + # Все интерфейсы всех устройств стека одним запросом + device_ids = [d.pk for d in devices] + interfaces_qs = Interface.objects.filter(device_id__in=device_ids) + + if commit: + # Вариант 1: цикл с .save() — вызываются clean(), save(), сигналы и change log (ObjectChange) + for iface in interfaces_qs: + iface.description = f'{iface.device.name}:{iface.name}' # пример + iface.save() + + # Вариант 2: массовый UPDATE без change log (быстро, но без аудита): + # interfaces_qs.update(description=...) # одно значение для всех + # Или bulk_update для разных значений по объектам: + # to_update = [] + # for iface in interfaces_qs: + # iface.description = f'{iface.device.name}:{iface.name}' + # to_update.append(iface) + # Interface.objects.bulk_update(to_update, ['description']) + else: + count = interfaces_qs.count() + self.log_success(f'Будет обновлено интерфейсов: {count}') + + +# vchassis = VirtualChassis.objects.all() +# for vc in vchassis: +# interfaces = Interface.objects.filter(device=vc) +# for interface in interfaces: +# interface.name = f'{vc.name}-{interface.name}' +# interface.save()