allow moving *virtual* interfaces between two devices in the same virtual chassis? #8478

Closed
opened 2025-12-29 20:37:14 +01:00 by adam · 2 comments
Owner

Originally created by @steffenkremser on GitHub (Aug 16, 2023).

NetBox version

v.3.5.7

Feature type

Change to existing functionality

Proposed functionality

the existig check at 16e2283d19/netbox/dcim/models/device_components.py (L101)

could be extended to allow "in-VC movement" of (virtual!) Interfaces

i tried "just adding another test" before the original, but ended up

  • pulling the "do we really have two distinct devices" test up because
    • it is a precondition for all subsequnt "is it allowed to move" rules
    • we can avoid false positive on self._original_device is None
  • starting a list of "for type X, it can be allowed if ..." rules
    (possibly with more specific value errors if not)
  • leaving the original raise ValidationError(..) clause at the bottom

like so:

    def clean(self):
        super().clean()

        # imports to be moved to top of file?
        from dcim.models import Device
        from dcim.choices import InterfaceTypeChoices

        # Check list of Modules that allow device field to be changed

        # self.device_id can't be None here (ValidationError in super.cleam())
        # BUT this could happen
        if self._original_device is None:
            # .. created without device
            # .. but added later on (before save)
            pass
            # avoid false positive on next comparison
        
        # TODO: (self.pk is None) .. how could THAT happen?
        # maybe "before first save" -> "change" is moot

        elif (self._original_device != self.device_id):
            # device_id has changed (from something != None)
            # .. which could be allowed for some types
            if (type(self) == Interface): 
                if (self.type in [InterfaceTypeChoices.TYPE_VIRTUAL]):  # TODO: bridge? LAG?
                    # collect virtual_chassis_id values 
                    vc_ids = set(
                        Device.objects.filter(
                            id__in=(self._original_device, self.device_id)
                        ).values_list(
                            "virtual_chassis",flat=True
                        )
                        # could have used .distinct() here
                    )
                    if (None in vc_ids):
                        # None present means at least one device is NOT in a VC 
                        raise ValidationError({
                          "device": f"{self.type} Interfaces can only move between vchassis members."
                        })                            
                    elif len(vc_ids)!=1:
                        # len!=1 means "not the same for both devices" 
                        raise ValidationError({
                          "device": f"{self.type} Interfaces can only move within the same vchassis."
                        })

            elif (type(self) in [InventoryItem]): #   and (self.pk is not None):
                # Inventory Items CAN move between devices
                pass
            else:
                # none of the "allowable" scenarios applied
                raise ValidationError({
                    "device": "Components cannot be moved to a different device."
                })

NOTE: i've skipped refactoring the self.pkvs None comparison (not covered by my testing)
i assume pk is None means the current object is freshly created/cloned
and without a "stored version" to diff against the "changed device" check is moot.

The updated check method seems to work (for my script)

.. but i wonder if "class-specific rules" should be moved into said classes instead

Use case

Given (multiple) virtual chassis each covering a set of (juniper) switches

  • with VC-master role flipping between two candidates at runtime
  • and candidate role ("vc priority") subject to change by admin

Over time, we accumulate "bitrot" like

  • duplicate services (like "ssh" or "snmp") added by users missing them on "newly elected" master
  • virtual interfaces ("vme" or "irb.nnn") hanging off various "former-master" members
    • all visible with .vc_interfaces() as done by GUI and (recently the REST API?)
    • visible to device.interface.all() only on the actual parent?

Eventually, i wrote a script to find and "reparent" those items

  • no problem for Service Objects
  • validation error for Interface Objects
    (actually, the script "worked OK" until we added the recommended calls to .snapshot() and .full_clean() )

The workaround is tedious and lossy

  • clone the Interface object using model_to_dict()
  • save once (to assign new id/pk value)
  • then re-apply m2m attributes (impossible without the new id) and save again
  • update all IPAddress objects where .assigned_object is the old interface
  • finally delete the old interface object (thereby losing its change history)

Database changes

No response

External dependencies

No response

Originally created by @steffenkremser on GitHub (Aug 16, 2023). ### NetBox version v.3.5.7 ### Feature type Change to existing functionality ### Proposed functionality the existig check at https://github.com/netbox-community/netbox/blob/16e2283d192c9726c5010144df576baa3b16ccd7/netbox/dcim/models/device_components.py#L101 could be extended to allow "in-VC movement" of (virtual!) Interfaces i tried "just adding another test" before the original, but ended up - pulling the "do we really have two distinct devices" test up because - it is a precondition for all subsequnt "is it allowed to move" rules - we can avoid false positive on `self._original_device is None` - starting a list of "for type X, it can be allowed if ..." rules (possibly with more specific value errors if not) - leaving the original `raise ValidationError(..)` clause at the bottom like so: ```python def clean(self): super().clean() # imports to be moved to top of file? from dcim.models import Device from dcim.choices import InterfaceTypeChoices # Check list of Modules that allow device field to be changed # self.device_id can't be None here (ValidationError in super.cleam()) # BUT this could happen if self._original_device is None: # .. created without device # .. but added later on (before save) pass # avoid false positive on next comparison # TODO: (self.pk is None) .. how could THAT happen? # maybe "before first save" -> "change" is moot elif (self._original_device != self.device_id): # device_id has changed (from something != None) # .. which could be allowed for some types if (type(self) == Interface): if (self.type in [InterfaceTypeChoices.TYPE_VIRTUAL]): # TODO: bridge? LAG? # collect virtual_chassis_id values vc_ids = set( Device.objects.filter( id__in=(self._original_device, self.device_id) ).values_list( "virtual_chassis",flat=True ) # could have used .distinct() here ) if (None in vc_ids): # None present means at least one device is NOT in a VC raise ValidationError({ "device": f"{self.type} Interfaces can only move between vchassis members." }) elif len(vc_ids)!=1: # len!=1 means "not the same for both devices" raise ValidationError({ "device": f"{self.type} Interfaces can only move within the same vchassis." }) elif (type(self) in [InventoryItem]): # and (self.pk is not None): # Inventory Items CAN move between devices pass else: # none of the "allowable" scenarios applied raise ValidationError({ "device": "Components cannot be moved to a different device." }) ``` NOTE: i've skipped refactoring the `self.pk`vs `None` comparison (not covered by my testing) i assume `pk is None` means the current object is freshly created/cloned and without a "stored version" to diff against the "changed device" check is moot. The updated check method seems to work (for my script) .. but i wonder if "class-specific rules" should be moved into said classes instead ### Use case Given (multiple) virtual chassis each covering a set of (juniper) switches - with VC-master role flipping between two candidates at runtime - and candidate role ("vc priority") subject to change by admin Over time, we accumulate "bitrot" like - duplicate services (like "ssh" or "snmp") added by users missing them on "newly elected" master - virtual interfaces ("vme" or "irb.nnn") hanging off various "former-master" members - all visible with `.vc_interfaces()` as done by GUI and (recently the REST API?) - visible to `device.interface.all()` only on the actual parent? Eventually, i wrote a script to find and "reparent" those items - no problem for `Service` Objects - validation error for `Interface` Objects (actually, the script "worked OK" _until_ we added the recommended calls to `.snapshot()` and `.full_clean()` ) The workaround is tedious and lossy - clone the Interface object using `model_to_dict()` - save once (to assign new id/pk value) - **then** re-apply m2m attributes (impossible without the new id) and save again - update all `IPAddress` objects where `.assigned_object` is the old interface - finally delete the old interface object (thereby losing its change history) ### Database changes _No response_ ### External dependencies _No response_
adam added the type: featurepending closure labels 2025-12-29 20:37:14 +01:00
adam closed this issue 2025-12-29 20:37:15 +01:00
Author
Owner

@github-actions[bot] commented on GitHub (Nov 15, 2023):

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. Do not attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our contributing guide.

@github-actions[bot] commented on GitHub (Nov 15, 2023): This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
Author
Owner

@github-actions[bot] commented on GitHub (Dec 15, 2023):

This issue has been automatically closed due to lack of activity. In an effort to reduce noise, please do not comment any further. Note that the core maintainers may elect to reopen this issue at a later date if deemed necessary.

@github-actions[bot] commented on GitHub (Dec 15, 2023): This issue has been automatically closed due to lack of activity. In an effort to reduce noise, please do not comment any further. Note that the core maintainers may elect to reopen this issue at a later date if deemed necessary.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#8478