forward-netbox 2.9.2__tar.gz → 2.9.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/PKG-INFO +7 -5
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/README.md +5 -3
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/__init__.py +1 -1
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/api/views.py +47 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/filtersets.py +21 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/forms.py +45 -3
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/jobs.py +157 -2
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_device_scope_reconciliation_audit.py +57 -1
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_profile_merge.py +57 -1
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/navigation.py +10 -1
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tables.py +41 -0
- forward_netbox-2.9.4/forward_netbox/template_content.py +153 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync.html +25 -2
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_scope_reconciliation.html +53 -6
- forward_netbox-2.9.4/forward_netbox/templates/forward_netbox/inc/device_ownership_panel.html +131 -0
- forward_netbox-2.9.4/forward_netbox/tests/test_bgp_peer_round_trip_variants.py +231 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_button_jobs.py +55 -19
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_cross_site_vlan_revalidation.py +6 -1
- forward_netbox-2.9.4/forward_netbox/tests/test_interface_lookup_query_shape.py +114 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_issue_diagnosis.py +158 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_log_export.py +439 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_optional_plugin_versions.py +2 -1
- forward_netbox-2.9.4/forward_netbox/tests/test_ospf_neighbour_rows_converge.py +149 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_plugin_integrations.py +4 -4
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_provision_preflight.py +13 -2
- forward_netbox-2.9.4/forward_netbox/tests/test_routing_apply_compare_round_trip.py +161 -0
- forward_netbox-2.9.4/forward_netbox/tests/test_row_collapsing.py +136 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_runtime_dependency_check.py +1 -1
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_module_ui.py +166 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_uncovered_absence_and_trend.py +123 -0
- forward_netbox-2.9.4/forward_netbox/tests/test_uncovered_device_cleanup.py +343 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_engine_bulk.py +63 -23
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/drift_comparison.py +6 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/fast_baseline.py +20 -2
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/health.py +3 -2
- forward_netbox-2.9.4/forward_netbox/utilities/ingestion_issues.py +74 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/interface_vlan_audit.py +72 -2
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/plugin_integrations/registry.py +4 -1
- forward_netbox-2.9.4/forward_netbox/utilities/row_collapsing.py +140 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/scope_reconciliation.py +303 -54
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/single_branch_executor.py +3 -3
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_facade.py +13 -1
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_reporting.py +7 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_routing_impl.py +10 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/validated_runtime.py +3 -1
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/workload_state.py +51 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/views.py +477 -4
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/pyproject.toml +2 -2
- forward_netbox-2.9.2/forward_netbox/template_content.py +0 -31
- forward_netbox-2.9.2/forward_netbox/utilities/ingestion_issues.py +0 -22
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/LICENSE +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/api/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/api/serializers.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/api/urls.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/choices.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/exceptions.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_accept_merge_failures.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_apic_cimc_readiness_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_apply_identity_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_blocker_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_changediff_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_collection_gap_alert.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_device_name_ambiguity_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_dlm_hardware_notice_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_fast_baseline_preflight.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_interface_vlan_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_link_vsys_parents.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_metrics.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_module_readiness.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_ownership_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_primary_ip_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_pushdown_profile.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_query_diff_coverage_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_resolve_query_ids.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_routing_dangling_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_scope_ipam_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_seed_ui_harness.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_smoke_sync.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_stuck_job_alert.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_stuck_job_recover.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_tag_delete_eligible.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_validation_org_query_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_warning_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/management/commands/forward_watch_sync.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0001_initial.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0002_add_coalesce_fields.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0003_forwardingestionissue_context_fields.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0004_forwardingestion_baseline_fields.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0005_validation_and_reporting.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0006_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0007_forwardingestion_persisted_change_counters.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0008_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0009_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0010_forwardvalidationrun_override_applied_and_more.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0011_forwardingestion_change_request_id.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0012_forwardnqemap_query_reference.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0013_execution_run_step.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0014_execution_step_fetch_scope.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0015_execution_step_apply_engine.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0016_execution_step_query_metrics.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0017_execution_step_row_counters.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0018_execution_run_reconciliation_events.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0019_forwardexecutionstep_query_parameters.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0020_execution_step_operation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0021_add_fhrpgroup_nqe_map_choice.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0022_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0023_split_ip_address_maps.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0024_forwarddeviceanalysis.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0025_device_analysis_netboxmodel.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0026_forwarddeviceanalysis_collection_result.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0027_alter_forwardexecutionrun_backend.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0028_remove_forwardexecutionstep_run_and_more.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0029_forward_parent_device_cf.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0030_forwarddeviceanalysis_cve_ids.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0031_encrypt_source_password.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0032_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0033_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0034_source_ownership_provenance.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0035_initialize_ownership_control_plane.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0036_protect_ownership_provenance.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0037_merge_identity_and_assignment_provenance.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0038_managed_tag_domain_guard.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0039_forwardingestion_catchup_state.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0040_remove_redundant_nqe_settings.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0041_forwardworkloadstate.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0042_device_generic_relation_guards.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0043_forwardnqemap_diff_contract.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0044_contributor_baseline_cache.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0045_forwardmergeattempt.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0046_alter_forwardnqemap_netbox_model.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0047_ownership_reconciliation_cascade.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0048_forwardingestion_skipped_change_count.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0049_drift_policy_row_shrink_floor.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0050_contributor_baseline_cascade.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0051_ownership_provenance_stamp_nullable.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/0052_device_absence_quarantine.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/migrations/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/models.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_apic_cimc_inventory.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_apic_nodes.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_bridge_domains.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_command_inventory.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_fabrics.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_filters.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_l3outs.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_nodes.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_pods.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_tenants.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_aci_vrfs.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_bgp_address_families.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_bgp_peer_address_families.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_bgp_peers.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_cimc_endpoint_inventory.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_config_backup.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_analysis.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_feature_tags.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_feature_tags_with_rules.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_models.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_models_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_types.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_device_vendors.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_devices.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_devices_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_apic_cimc_inventory_item_software.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_cves.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_device_software.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_hardware_notices.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_hardware_notices_with_netbox_aliases.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_inventory_item_software.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_software_versions.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_dlm_vulnerabilities.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_hsrp_groups.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_inferred_interface_cables.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_interfaces.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_inventory_items.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ip_addresses_ipv4.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ip_addresses_ipv6.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ip_addresses_unassignable_diagnostics.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_locations.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_mac_addresses.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_modules.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ospf_areas.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ospf_instances.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_ospf_interfaces.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_peering_sessions.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_platforms.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_prefixes_ipv4.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_prefixes_ipv6.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_routing_import_diagnostics.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_virtual_chassis.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_vlans.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/forward_vrfs.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/queries/netbox_utilities.nqe +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/signals.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/static/forward_netbox/img/fn-logo.svg +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/device_cves.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwarddeviceanalysis.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwarddriftpolicy.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardingestion.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardingestionissue.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardnqemap.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsource.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_dependency_preview.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_drift_report.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_health.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_module_readiness.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardsync_uncovered_devices.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/forwardvalidationrun.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/brand_bar.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/device_analysis_panel.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/diff.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/execution_insights.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/logs_pending.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/merge_form.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/inc/validation_force_allow_form.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/change_explainability.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_all.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_merge_button.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_progress.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_statistics.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/ingestion_status.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/job_logs.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/object_tabs.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/stage_switcher_buttons.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templates/forward_netbox/partials/validation_force_allow_button.html +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templatetags/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/templatetags/forward_netbox_helpers.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/fixtures/aci_command_inventory_expected.json +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/fixtures/aci_discovery_expected.json +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_a_warning_is_not_a_failure.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_absence_quarantine.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_absent_device_does_not_block_tag_domain.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_accepted_merge_failures.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_aci_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_adapter_drift_scale.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_api_serializer_base.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_api_usage.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_api_views.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_apic_cimc_readiness_audit_command.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_apply_engine.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_apply_identity_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_bgp_peer_comment_churn.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_branch_budget_split.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_branching.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_brand_bar.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_builtin_query_id_binding.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_bulk_adapter_parity.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_bulk_merge.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_bulk_merge_scale.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_cable_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_changediff_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_collection_collapse_guard.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_config_backup.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_contract_preflight_message.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_contributor_baseline.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_converged_models_are_measured.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_copy_sql_apply_engine.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_crypto.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_density_observation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dependency_preview_summary.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_device_analysis_query.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_device_scope_reconciliation_audit_command.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_device_scope_tagging.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_diagnostics_readback.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_diff_removal_allowlist.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_catalogue_conflicts.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_hardware_notice_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_integration.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_dlm_inventoryitem_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_drift_coverage_is_explained.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_drift_rows_are_distinguishable.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_empty_comparison_is_not_a_measurement.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_empty_workload_failure.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_endpoints_import.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_execute_by_query_id_without_commit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_execution_insights_delete_label.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_failed_run_model_evidence.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_fast_baseline.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_fast_paths_report_when_disabled.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_fhrpgroup_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_field_reported_integrity_failures.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_force_allow_override.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_forms.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_forward_api.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_full_removal_reconciliation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_head_commit_from_listing.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_health.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ingestion_delete.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ingestion_is_deletable.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ingestion_merge.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_interface_naming.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_interface_vlan_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_inventoryitem_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ip_address_dedup_query.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ipaddress_dup_global.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_issue_rendering.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_job_liveness.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_job_queue.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_jobs.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_keyerror_names_its_key.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_license_tier.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_logging.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_merge_observability.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_merge_profiling.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_merge_rule_rejection.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_migration_dependencies.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_migrations.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_mixed_model_change_diffs.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_model_view_routes.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_models.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_module_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_module_readiness.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_module_readiness_rollup.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_operator_commands.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ospf_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership_command.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership_completes_with_preexisting_status_tag.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership_conflict_reason.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_ownership_migration.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_peering_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_post_sync.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_preview_isolation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_preview_primes_its_lookup_caches.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_preview_reports_its_cost.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_preview_runner_satisfies_the_priming_contract.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_primary_ip.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_primary_ip_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_primary_ip_integration.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_protected_delete_is_a_skip.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_protecting_reference_deletes.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_protecting_relations.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_quarantine_cadence.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_binding.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_diff_coverage_audit_command.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_execution_contract.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_id_binding_end_to_end.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_registry.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_query_variants.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_raise_site_is_recorded.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_refused_deletes_are_not_staged.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_refused_deletes_are_retried.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_release_readiness_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_reserved_status_tag_adoption.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_row_count_floor.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scheduled_jobs.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_audit_full.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_ipam_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_matched_tags.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_reconciliation_view.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_shrink_guard.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_scope_tag_slug_collision.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_set_based_merge.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_skip_dependency_direction.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_skip_names_netbox_row.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_skip_raisers_name_dependency.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_skip_rollup_direction.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_smoke_sync_command.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_source_parameter_keys_agree.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_stability_hardening.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_stuck_recovery.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_support_bundle_archive.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_aci.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_facade.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_orchestration.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_recurrence_pinning.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_runner_contracts.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_sync_state.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_taggeditem_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_tier3_reducers.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_tree_model_detection.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_uncovered_device_tag.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_upgrade_reconciliation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_validated_runtime_is_declared_once.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_validation_org_query_audit_command.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_virtualchassis_drift_comparison.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_vsys_parent.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_workload_normalization.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/tests/test_workload_state.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/urls.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/api_usage.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_engine.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_engine_copy_sql.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_engine_decision.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/apply_identity_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/branch_budget.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/branch_lifecycle.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/branching.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/bulk_delete.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/bulk_merge.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/change_explainability.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/changediff_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/config_backup.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/contributor_baseline.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/crypto.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/delete_policy.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/density_learning.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/device_analysis.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/diagnostics.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/direct_changes.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/dlm_notice_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/drift_report.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/execution_telemetry.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/executor_base.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/fast_baseline_models.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/forward_api.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/forward_api_impl.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/full_removal_reconciliation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/health_apply_fetch.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/health_checks.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/health_summary_blocks.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/ingestion_merge.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/ingestion_presentation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/interface_naming.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/job_liveness.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/job_queue.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/json_safe.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/license_tier.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/logging.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/merge.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/merge_observability.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/merge_profiling.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/merge_set_based.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/model_contracts.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/model_validation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/module_readiness.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/ownership.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/plugin_integrations/__init__.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/post_sync.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/primary_ip.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/primary_ip_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_binding.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_binding_resolution.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_diagnostics.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_execution_contract.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_fetch.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_fetch_execution.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/query_registry.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/runtime_guidance.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/scope_ipam_audit.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/snapshot_freshness.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/stuck_recovery.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/support_bundle_archive.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_aci.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_cable.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_contracts.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_core_models.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_device.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_dlm.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_events.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_execution.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_interface.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_inventory_module.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_ipam.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_orchestration.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_primitives.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_routing.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_runner_adapters.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_runner_contracts.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/sync_state.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/tag_contracts.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/tier3_reducers.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/upgrade_reconciliation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/validation.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/validation_org_query.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/version_series.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/vsys_parent.py +0 -0
- {forward_netbox-2.9.2 → forward_netbox-2.9.4}/forward_netbox/utilities/workload_normalization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forward-netbox
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.4
|
|
4
4
|
Summary: NetBox plugin to sync Forward data into NetBox via built-in NQE queries
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -29,7 +29,7 @@ Provides-Extra: routing
|
|
|
29
29
|
Provides-Extra: validity
|
|
30
30
|
Requires-Dist: httpx (>0.26,<0.29)
|
|
31
31
|
Requires-Dist: netbox-cisco-aci (==0.4.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "aci" or extra == "integrations")
|
|
32
|
-
Requires-Dist: netbox-dlm (>=0.4.1,<0.
|
|
32
|
+
Requires-Dist: netbox-dlm (>=0.4.1,<0.11.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "dlm" or extra == "integrations")
|
|
33
33
|
Requires-Dist: netbox-peering-manager (==0.3.0) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "peering" or extra == "integrations")
|
|
34
34
|
Requires-Dist: netbox-routing (==0.4.3) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "routing" or extra == "integrations")
|
|
35
35
|
Requires-Dist: netbox-validity (==3.5.2) ; (python_version >= "3.12" and python_version < "3.15") and (extra == "validity" or extra == "integrations")
|
|
@@ -108,15 +108,17 @@ See the
|
|
|
108
108
|
|
|
109
109
|
## Release Compatibility
|
|
110
110
|
|
|
111
|
-
The `2.9.
|
|
111
|
+
The `2.9.4` release candidate requires NetBox `4.6.x` (>= `4.6.5`, tested on `4.6.8`) and `netbox-branching` `1.1.x` (tested on `1.1.2`). Expand for the published release history and candidate notes.
|
|
112
112
|
|
|
113
113
|
<details>
|
|
114
114
|
<summary>Release compatibility history</summary>
|
|
115
115
|
|
|
116
116
|
| Plugin Release | NetBox Version | Status |
|
|
117
117
|
| --- | --- | --- |
|
|
118
|
-
| `v2.9.
|
|
119
|
-
| `v2.9.
|
|
118
|
+
| `v2.9.4` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Release candidate; Fix: every operator diagnostic and remediation is now reachable in the NetBox GUI - the uncovered-device cleanup 2.9.3 shipped as a command flag is a gated button, a wedged sync has a recovery control, ingestion issues say which of them block the baseline, the two device sets the panel counted but would not list are listable in full, a device that refuses a manual delete explains which records refuse it and which of those this plugin can release, and the support bundle carries all of it so a problem we have to look at arrives with its evidence. |
|
|
119
|
+
| `v2.9.3` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Current release; Feature: **uncovered devices can be cleaned up**, not only counted - `forward_device_scope_reconciliation_audit --prune-uncovered` deletes devices this sync created that Forward no longer reports at all. The orphan prune never reached them: it acts on devices claimed by the run that produced the current result, so a deployment whose orphan count reads zero had no way to clear a growing uncovered count. Only devices the census marks absent are eligible, and only once their absence has outlasted the quarantine - a device Forward still reports without an include tag is a scoping decision and is never deleted, and neither is one this sync did not create. Dry run unless `--apply` is passed. Fix: **OSPF interface rows converge instead of drifting forever**, and the interface lookup behind MAC-address apply uses an index instead of scanning every interface in the estate. Feature: **netbox-dlm `0.10.0` is supported**. No migration; upgrade and re-run the sync. |
|
|
120
|
+
| `v2.9.2` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.3`; Drift is measured exactly for every fetched model, the uncovered-device count says why it grows and warns when it does, and five delete and diagnostic defects are closed. |
|
|
121
|
+
| `v2.9.1` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.2`; Accepts netbox-branching 1.1.3 and records the validated runtime in the lockfile. |
|
|
120
122
|
| `v2.9.0` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.1`; Feature: back up device configurations from Forward into a git data source for Validity golden-config checks, plus the runtime-validation refactor and health check it required. |
|
|
121
123
|
| `v2.8.9` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.0`; Fix: a sync deletes a device only on the same quarantined evidence the operator prune requires, and never stages a delete the database will refuse. |
|
|
122
124
|
| `v2.8.8` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.8.9`; Fix: the dependency preview runs again on deployments with optional plugins installed. |
|
|
@@ -64,15 +64,17 @@ See the
|
|
|
64
64
|
|
|
65
65
|
## Release Compatibility
|
|
66
66
|
|
|
67
|
-
The `2.9.
|
|
67
|
+
The `2.9.4` release candidate requires NetBox `4.6.x` (>= `4.6.5`, tested on `4.6.8`) and `netbox-branching` `1.1.x` (tested on `1.1.2`). Expand for the published release history and candidate notes.
|
|
68
68
|
|
|
69
69
|
<details>
|
|
70
70
|
<summary>Release compatibility history</summary>
|
|
71
71
|
|
|
72
72
|
| Plugin Release | NetBox Version | Status |
|
|
73
73
|
| --- | --- | --- |
|
|
74
|
-
| `v2.9.
|
|
75
|
-
| `v2.9.
|
|
74
|
+
| `v2.9.4` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Release candidate; Fix: every operator diagnostic and remediation is now reachable in the NetBox GUI - the uncovered-device cleanup 2.9.3 shipped as a command flag is a gated button, a wedged sync has a recovery control, ingestion issues say which of them block the baseline, the two device sets the panel counted but would not list are listable in full, a device that refuses a manual delete explains which records refuse it and which of those this plugin can release, and the support bundle carries all of it so a problem we have to look at arrives with its evidence. |
|
|
75
|
+
| `v2.9.3` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Current release; Feature: **uncovered devices can be cleaned up**, not only counted - `forward_device_scope_reconciliation_audit --prune-uncovered` deletes devices this sync created that Forward no longer reports at all. The orphan prune never reached them: it acts on devices claimed by the run that produced the current result, so a deployment whose orphan count reads zero had no way to clear a growing uncovered count. Only devices the census marks absent are eligible, and only once their absence has outlasted the quarantine - a device Forward still reports without an include tag is a scoping decision and is never deleted, and neither is one this sync did not create. Dry run unless `--apply` is passed. Fix: **OSPF interface rows converge instead of drifting forever**, and the interface lookup behind MAC-address apply uses an index instead of scanning every interface in the estate. Feature: **netbox-dlm `0.10.0` is supported**. No migration; upgrade and re-run the sync. |
|
|
76
|
+
| `v2.9.2` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.3`; Drift is measured exactly for every fetched model, the uncovered-device count says why it grows and warns when it does, and five delete and diagnostic defects are closed. |
|
|
77
|
+
| `v2.9.1` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.2`; Accepts netbox-branching 1.1.3 and records the validated runtime in the lockfile. |
|
|
76
78
|
| `v2.9.0` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.1`; Feature: back up device configurations from Forward into a git data source for Validity golden-config checks, plus the runtime-validation refactor and health check it required. |
|
|
77
79
|
| `v2.8.9` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.9.0`; Fix: a sync deletes a device only on the same quarantined evidence the operator prune requires, and never stages a delete the database will refuse. |
|
|
78
80
|
| `v2.8.8` | `4.6.x` (>= `4.6.5`) supported, tested on `4.6.8`; needs netbox-branching `1.1.x`, tested on `1.1.2` | Superseded by `v2.8.9`; Fix: the dependency preview runs again on deployments with optional plugins installed. |
|
|
@@ -5,7 +5,7 @@ class NetboxForwardConfig(PluginConfig):
|
|
|
5
5
|
name = "forward_netbox"
|
|
6
6
|
verbose_name = "Forward"
|
|
7
7
|
description = "Sync Forward data into NetBox using built-in NQE queries."
|
|
8
|
-
version = "2.9.
|
|
8
|
+
version = "2.9.4"
|
|
9
9
|
base_url = "forward"
|
|
10
10
|
min_version = "4.6.5"
|
|
11
11
|
max_version = "4.6.99"
|
|
@@ -928,6 +928,53 @@ class ForwardSyncViewSet(NetBoxModelViewSet):
|
|
|
928
928
|
def tag_delete_eligible_ipam(self, request, pk):
|
|
929
929
|
return self._enqueue_button_job_response(request, "tag_delete_eligible_ipam")
|
|
930
930
|
|
|
931
|
+
@extend_schema(
|
|
932
|
+
methods=["post"],
|
|
933
|
+
request=EmptySerializer(),
|
|
934
|
+
responses={
|
|
935
|
+
201: JobSerializer(),
|
|
936
|
+
202: OpenApiResponse(
|
|
937
|
+
description='{"status": "already_running", "job_id": N}'
|
|
938
|
+
),
|
|
939
|
+
},
|
|
940
|
+
)
|
|
941
|
+
@action(detail=True, methods=["post"], url_path="recover-stuck-sync")
|
|
942
|
+
def recover_stuck_sync(self, request, pk):
|
|
943
|
+
return self._enqueue_button_job_response(request, "recover_stuck_sync")
|
|
944
|
+
|
|
945
|
+
@extend_schema(
|
|
946
|
+
methods=["post"],
|
|
947
|
+
request=EmptySerializer(),
|
|
948
|
+
responses={
|
|
949
|
+
201: JobSerializer(),
|
|
950
|
+
202: OpenApiResponse(
|
|
951
|
+
description=(
|
|
952
|
+
'{"status": "already_running"|"blocked_by_sync_run", '
|
|
953
|
+
'"job_id": N}'
|
|
954
|
+
)
|
|
955
|
+
),
|
|
956
|
+
},
|
|
957
|
+
)
|
|
958
|
+
@action(detail=True, methods=["post"], url_path="prune-uncovered")
|
|
959
|
+
def prune_uncovered(self, request, pk):
|
|
960
|
+
return self._enqueue_button_job_response(request, "prune_uncovered")
|
|
961
|
+
|
|
962
|
+
@extend_schema(
|
|
963
|
+
methods=["post"],
|
|
964
|
+
request=EmptySerializer(),
|
|
965
|
+
responses={
|
|
966
|
+
201: JobSerializer(),
|
|
967
|
+
202: OpenApiResponse(
|
|
968
|
+
description='{"status": "already_running", "job_id": N}'
|
|
969
|
+
),
|
|
970
|
+
},
|
|
971
|
+
)
|
|
972
|
+
@action(detail=True, methods=["post"], url_path="prune-stale-hardware-notices")
|
|
973
|
+
def prune_stale_hardware_notices(self, request, pk):
|
|
974
|
+
return self._enqueue_button_job_response(
|
|
975
|
+
request, "prune_stale_hardware_notices"
|
|
976
|
+
)
|
|
977
|
+
|
|
931
978
|
|
|
932
979
|
class ForwardIngestionViewSet(NetBoxReadOnlyModelViewSet):
|
|
933
980
|
queryset = ForwardIngestion.objects.all()
|
|
@@ -162,11 +162,32 @@ class ForwardIngestionIssueFilterSet(BaseFilterSet):
|
|
|
162
162
|
choices=ForwardIngestionPhaseChoices,
|
|
163
163
|
null_value=None,
|
|
164
164
|
)
|
|
165
|
+
# "Which of these actually stops the baseline" was answerable only by
|
|
166
|
+
# running a command against the ingestion. Same predicate as readiness.
|
|
167
|
+
blocking = django_filters.BooleanFilter(
|
|
168
|
+
method="filter_blocking",
|
|
169
|
+
label="Blocks baseline readiness",
|
|
170
|
+
)
|
|
165
171
|
|
|
166
172
|
class Meta:
|
|
167
173
|
model = ForwardIngestionIssue
|
|
168
174
|
fields = ("phase", "model", "timestamp", "exception", "message")
|
|
169
175
|
|
|
176
|
+
def filter_blocking(self, queryset, name, value):
|
|
177
|
+
from django.db.models import Q
|
|
178
|
+
|
|
179
|
+
from .utilities.ingestion_issues import blocking_issue_q
|
|
180
|
+
|
|
181
|
+
if value is None:
|
|
182
|
+
return queryset
|
|
183
|
+
# Matches the column: a row whose ingestion promoted its baseline
|
|
184
|
+
# blocked nothing, so it is not offered as blocking however its class
|
|
185
|
+
# is scored. Filtering to rows the column calls Blocking and getting
|
|
186
|
+
# back rows it calls Promoted over is the same disagreement in the
|
|
187
|
+
# other direction.
|
|
188
|
+
predicate = blocking_issue_q() & Q(ingestion__baseline_ready=False)
|
|
189
|
+
return queryset.filter(predicate if value else ~predicate)
|
|
190
|
+
|
|
170
191
|
def search(self, queryset, name, value):
|
|
171
192
|
if not value.strip():
|
|
172
193
|
return queryset
|
|
@@ -6,6 +6,7 @@ from netbox.forms import NetBoxModelForm
|
|
|
6
6
|
from utilities.datetime import local_now
|
|
7
7
|
from utilities.forms import add_blank_choice
|
|
8
8
|
from utilities.forms import ConfirmationForm
|
|
9
|
+
from utilities.forms import FilterForm
|
|
9
10
|
from utilities.forms import get_field_value
|
|
10
11
|
from utilities.forms.fields import CommentField
|
|
11
12
|
from utilities.forms.rendering import FieldSet
|
|
@@ -18,12 +19,14 @@ from utilities.forms.widgets import NumberWithOptions
|
|
|
18
19
|
from .choices import forward_configured_models
|
|
19
20
|
from .choices import FORWARD_OPTIONAL_MODELS
|
|
20
21
|
from .choices import ForwardDiffFallbackModeChoices
|
|
22
|
+
from .choices import ForwardIngestionPhaseChoices
|
|
21
23
|
from .choices import ForwardSourceDeploymentChoices
|
|
22
24
|
from .choices import ForwardSourceStatusChoices
|
|
23
25
|
from .choices import ForwardSyncStatusChoices
|
|
24
26
|
from .exceptions import ForwardConnectivityError
|
|
25
27
|
from .exceptions import ForwardSyncError
|
|
26
28
|
from .models import ForwardDriftPolicy
|
|
29
|
+
from .models import ForwardIngestionIssue
|
|
27
30
|
from .models import ForwardNQEMap
|
|
28
31
|
from .models import ForwardSource
|
|
29
32
|
from .models import ForwardSync
|
|
@@ -1210,9 +1213,11 @@ class ForwardSyncForm(NetBoxModelForm):
|
|
|
1210
1213
|
label="Use fast first-baseline load",
|
|
1211
1214
|
help_text=(
|
|
1212
1215
|
"Exact-version, first-full-snapshot opt-in for an empty NetBox. "
|
|
1213
|
-
"It omits branch review, per-row audit evidence, and branch
|
|
1214
|
-
"
|
|
1215
|
-
"
|
|
1216
|
+
"It omits branch review, per-row audit evidence, and branch "
|
|
1217
|
+
"rollback, so a bad first load cannot be unwound - verify a "
|
|
1218
|
+
"database backup before enabling it. Only use it on a NetBox with "
|
|
1219
|
+
"no inventory of its own, and leave it off for every run after the "
|
|
1220
|
+
"first."
|
|
1216
1221
|
),
|
|
1217
1222
|
)
|
|
1218
1223
|
require_fast_baseline_eligibility = forms.BooleanField(
|
|
@@ -2065,3 +2070,40 @@ class ForwardDriftPolicyBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
2065
2070
|
enabled = forms.NullBooleanField(required=False, label="Enabled")
|
|
2066
2071
|
model = ForwardDriftPolicy
|
|
2067
2072
|
fields = ("enabled",)
|
|
2073
|
+
|
|
2074
|
+
|
|
2075
|
+
class ForwardIngestionIssueFilterForm(FilterForm):
|
|
2076
|
+
"""Filter tab for the ingestion-issue list.
|
|
2077
|
+
|
|
2078
|
+
The plugin's other list views carry no filter form, so this one is here for
|
|
2079
|
+
a specific reason: `blocking` is the only field on the page that separates
|
|
2080
|
+
the handful of rows holding a baseline back from the several hundred that
|
|
2081
|
+
are noise, and a filter reachable only by hand-editing the query string is
|
|
2082
|
+
not reachable. `forward_blocker_audit` was the previous answer.
|
|
2083
|
+
"""
|
|
2084
|
+
|
|
2085
|
+
model = ForwardIngestionIssue
|
|
2086
|
+
fieldsets = (
|
|
2087
|
+
FieldSet("q", "blocking"),
|
|
2088
|
+
FieldSet("phase", "exception", name="Failure"),
|
|
2089
|
+
)
|
|
2090
|
+
q = forms.CharField(required=False, label="Search")
|
|
2091
|
+
blocking = forms.NullBooleanField(
|
|
2092
|
+
required=False,
|
|
2093
|
+
label="Blocks baseline readiness",
|
|
2094
|
+
widget=forms.Select(
|
|
2095
|
+
choices=(
|
|
2096
|
+
("", "---------"),
|
|
2097
|
+
("true", "Blocking only"),
|
|
2098
|
+
("false", "Non-blocking only"),
|
|
2099
|
+
)
|
|
2100
|
+
),
|
|
2101
|
+
)
|
|
2102
|
+
phase = forms.MultipleChoiceField(
|
|
2103
|
+
choices=ForwardIngestionPhaseChoices,
|
|
2104
|
+
required=False,
|
|
2105
|
+
)
|
|
2106
|
+
# No field for the NetBox model: `model` is FilterForm's own attribute for
|
|
2107
|
+
# the model being filtered, and a form field of that name shadows it. The
|
|
2108
|
+
# search box already matches `model` (see the filterset's `search`).
|
|
2109
|
+
exception = forms.CharField(required=False, label="Exception")
|
|
@@ -516,8 +516,8 @@ def _enqueue_post_sync_overlays(
|
|
|
516
516
|
exception_type(exc),
|
|
517
517
|
)
|
|
518
518
|
raise ForwardOwnershipDispatchError(
|
|
519
|
-
"Ownership reconciliation is durable but could not be enqueued
|
|
520
|
-
"
|
|
519
|
+
"Ownership reconciliation is durable but could not be enqueued. "
|
|
520
|
+
"Use Recover stuck sync on this sync's page to redispatch it."
|
|
521
521
|
) from exc
|
|
522
522
|
|
|
523
523
|
|
|
@@ -864,6 +864,135 @@ def _validate_forwardsync_work(job):
|
|
|
864
864
|
raise
|
|
865
865
|
|
|
866
866
|
|
|
867
|
+
def _recover_stuck_sync_work(job):
|
|
868
|
+
"""Recover one sync wedged by a worker that died mid-run.
|
|
869
|
+
|
|
870
|
+
An RQ worker killed during a merge leaves the sync `MERGING` with no live
|
|
871
|
+
execution behind it, and nothing times out. The classification and the
|
|
872
|
+
recovery already existed and were reachable only from a command, so the
|
|
873
|
+
only person who could unstick a sync was one with shell access - and the
|
|
874
|
+
failure that causes it is announced in a job log, where a shell is exactly
|
|
875
|
+
what the reader does not have.
|
|
876
|
+
"""
|
|
877
|
+
from .utilities.stuck_recovery import classify_stuck_sync
|
|
878
|
+
from .utilities.stuck_recovery import recover_stuck_sync
|
|
879
|
+
|
|
880
|
+
sync = ForwardSync.objects.get(pk=job.object_id)
|
|
881
|
+
verdict = classify_stuck_sync(sync)
|
|
882
|
+
if verdict is None:
|
|
883
|
+
# Not an error: the operator clicked a button offered by a report that
|
|
884
|
+
# has since gone stale, or another recovery got there first.
|
|
885
|
+
job.data = {
|
|
886
|
+
"recovered": False,
|
|
887
|
+
"reason": "nothing-stuck",
|
|
888
|
+
"detail": (
|
|
889
|
+
"This sync has no wedged job to recover. It either recovered on "
|
|
890
|
+
"its own or another recovery already ran."
|
|
891
|
+
),
|
|
892
|
+
}
|
|
893
|
+
job.save(update_fields=["data"])
|
|
894
|
+
return
|
|
895
|
+
|
|
896
|
+
result = recover_stuck_sync(sync, verdict, user=job.user)
|
|
897
|
+
job.data = {
|
|
898
|
+
"recovered": True,
|
|
899
|
+
"action": verdict.get("action"),
|
|
900
|
+
"reason": verdict.get("reason"),
|
|
901
|
+
"result": result,
|
|
902
|
+
}
|
|
903
|
+
job.save(update_fields=["data"])
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def _prune_uncovered_devices_work(job, *, include_quarantined=False):
|
|
907
|
+
"""Run reviewed uncovered-device pruning for a JobRunner-managed sync job.
|
|
908
|
+
|
|
909
|
+
Same shape as the orphan prune, and the same reason for the flag: it
|
|
910
|
+
arrives only from the operator button, and only when that operator ticked
|
|
911
|
+
the box beside a named list of held devices. Nothing schedules this, so
|
|
912
|
+
there is no unattended path that could override the quarantine.
|
|
913
|
+
"""
|
|
914
|
+
from .utilities.scope_reconciliation import compute_scope_reconciliation
|
|
915
|
+
from .utilities.scope_reconciliation import EmptyForwardScopeError
|
|
916
|
+
from .utilities.scope_reconciliation import prune_uncovered_devices
|
|
917
|
+
from .utilities.scope_reconciliation import ScopeCensusUnavailableError
|
|
918
|
+
from .utilities.scope_reconciliation import ScopeShrinkGuardError
|
|
919
|
+
|
|
920
|
+
sync = ForwardSync.objects.get(pk=job.object_id)
|
|
921
|
+
try:
|
|
922
|
+
report = compute_scope_reconciliation(sync)
|
|
923
|
+
result = prune_uncovered_devices(
|
|
924
|
+
sync,
|
|
925
|
+
report=report,
|
|
926
|
+
include_quarantined=include_quarantined,
|
|
927
|
+
)
|
|
928
|
+
job.data = {
|
|
929
|
+
"pruned_device_count": result.get("pruned_device_count", 0),
|
|
930
|
+
"pruned_object_count": result.get("pruned_object_count", 0),
|
|
931
|
+
"out_of_scope_sample": result.get("out_of_scope_sample", []),
|
|
932
|
+
"ownership_blocked_device_count": result.get(
|
|
933
|
+
"ownership_blocked_device_count", 0
|
|
934
|
+
),
|
|
935
|
+
"protected_device_count": result.get("protected_device_count", 0),
|
|
936
|
+
# WHICH model refused, not just how many devices were refused. The
|
|
937
|
+
# orphan prune has carried this since 2.5.5 and this one shipped
|
|
938
|
+
# without it, so a device held by another plugin's rows - ten
|
|
939
|
+
# netbox_routing BGP peers on its addresses, in the case that found
|
|
940
|
+
# this - reported a bare count and named nothing to act on.
|
|
941
|
+
"protected_by_model": result.get("protected_by_model", {}),
|
|
942
|
+
"quarantine_held_device_count": result.get(
|
|
943
|
+
"quarantine_held_device_count", 0
|
|
944
|
+
),
|
|
945
|
+
"quarantine_overridden_device_count": result.get(
|
|
946
|
+
"quarantine_overridden_device_count", 0
|
|
947
|
+
),
|
|
948
|
+
"quarantine_required_runs": result.get("quarantine_required_runs"),
|
|
949
|
+
"quarantine_required_hours": result.get("quarantine_required_hours"),
|
|
950
|
+
}
|
|
951
|
+
job.save(update_fields=["data"])
|
|
952
|
+
except ScopeCensusUnavailableError as exc:
|
|
953
|
+
# Not a failure to hide behind a class name: without the census nothing
|
|
954
|
+
# is known about WHY these devices are uncovered, and absent is the only
|
|
955
|
+
# eligible reason. The message says exactly that.
|
|
956
|
+
job.data = {"error": str(exc), "error_type": exception_type(exc)}
|
|
957
|
+
job.save(update_fields=["data"])
|
|
958
|
+
logger.error(
|
|
959
|
+
"Forward uncovered pruning refused without a census (%s).",
|
|
960
|
+
exception_type(exc),
|
|
961
|
+
)
|
|
962
|
+
raise
|
|
963
|
+
except ScopeShrinkGuardError as exc:
|
|
964
|
+
# Counts and a percentage only, never a device name, so the operator
|
|
965
|
+
# gets the reason rather than a class name.
|
|
966
|
+
job.data = {
|
|
967
|
+
"error": str(exc),
|
|
968
|
+
"error_type": exception_type(exc),
|
|
969
|
+
}
|
|
970
|
+
job.save(update_fields=["data"])
|
|
971
|
+
logger.error(
|
|
972
|
+
"Forward uncovered pruning refused a shrinking scope (%s).",
|
|
973
|
+
exception_type(exc),
|
|
974
|
+
)
|
|
975
|
+
raise
|
|
976
|
+
except EmptyForwardScopeError as exc:
|
|
977
|
+
job.data = {
|
|
978
|
+
"error": safe_operation_failure("Forward uncovered pruning", exc),
|
|
979
|
+
"error_type": exception_type(exc),
|
|
980
|
+
}
|
|
981
|
+
job.save(update_fields=["data"])
|
|
982
|
+
logger.error(
|
|
983
|
+
"Forward uncovered pruning rejected an empty scope (%s).",
|
|
984
|
+
exception_type(exc),
|
|
985
|
+
)
|
|
986
|
+
raise
|
|
987
|
+
except Exception as exc:
|
|
988
|
+
job.data = {
|
|
989
|
+
"error": safe_operation_failure("Forward uncovered pruning", exc),
|
|
990
|
+
"error_type": exception_type(exc),
|
|
991
|
+
}
|
|
992
|
+
job.save(update_fields=["data"])
|
|
993
|
+
raise
|
|
994
|
+
|
|
995
|
+
|
|
867
996
|
def _prune_forward_orphans_work(job, *, include_quarantined=False):
|
|
868
997
|
"""Run reviewed orphan pruning for a JobRunner-managed sync job.
|
|
869
998
|
|
|
@@ -1989,6 +2118,32 @@ class VirtualParentReconciliationJob(ForwardJobRunner):
|
|
|
1989
2118
|
_link_forward_vsys_parents_work(self.job, *args, **kwargs)
|
|
1990
2119
|
|
|
1991
2120
|
|
|
2121
|
+
class RecoverStuckSyncJob(ForwardJobRunner):
|
|
2122
|
+
"""Operator-triggered recovery of a sync wedged by a dead worker."""
|
|
2123
|
+
|
|
2124
|
+
class Meta:
|
|
2125
|
+
# Byte-identical to BUTTON_JOB_SPECS["recover_stuck_sync"][1].
|
|
2126
|
+
name = "recover stuck sync"
|
|
2127
|
+
|
|
2128
|
+
def run(self, *args, **kwargs):
|
|
2129
|
+
_recover_stuck_sync_work(self.job)
|
|
2130
|
+
|
|
2131
|
+
|
|
2132
|
+
class PruneUncoveredDevicesJob(ForwardJobRunner):
|
|
2133
|
+
"""Reviewed uncovered-device prune runner used by the HTML action."""
|
|
2134
|
+
|
|
2135
|
+
class Meta:
|
|
2136
|
+
# Byte-identical to BUTTON_JOB_SPECS["prune_uncovered"][1]: the overlap
|
|
2137
|
+
# guard's exact-name arm depends on it.
|
|
2138
|
+
name = "prune uncovered devices"
|
|
2139
|
+
|
|
2140
|
+
def run(self, *args, **kwargs):
|
|
2141
|
+
extra = {}
|
|
2142
|
+
if "include_quarantined" in kwargs:
|
|
2143
|
+
extra["include_quarantined"] = bool(kwargs["include_quarantined"])
|
|
2144
|
+
_prune_uncovered_devices_work(self.job, **extra)
|
|
2145
|
+
|
|
2146
|
+
|
|
1992
2147
|
class PruneOrphansJob(ForwardJobRunner):
|
|
1993
2148
|
"""Reviewed orphan-prune runner used by the HTML and REST actions."""
|
|
1994
2149
|
|
|
@@ -6,6 +6,7 @@ from django.core.management.base import CommandError
|
|
|
6
6
|
from forward_netbox.models import ForwardSync
|
|
7
7
|
from forward_netbox.utilities.scope_reconciliation import compute_scope_reconciliation
|
|
8
8
|
from forward_netbox.utilities.scope_reconciliation import prune_orphan_devices
|
|
9
|
+
from forward_netbox.utilities.scope_reconciliation import prune_uncovered_devices
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class Command(BaseCommand):
|
|
@@ -33,10 +34,25 @@ class Command(BaseCommand):
|
|
|
33
34
|
"also passed. Tagged-but-backfilled devices are never pruned."
|
|
34
35
|
),
|
|
35
36
|
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--prune-uncovered",
|
|
39
|
+
action="store_true",
|
|
40
|
+
help=(
|
|
41
|
+
"Delete devices this sync created that Forward no longer "
|
|
42
|
+
"reports at all. Only devices the census marks absent are "
|
|
43
|
+
"eligible: one Forward still returns without an include tag is "
|
|
44
|
+
"a scoping question and is never touched, and a device this "
|
|
45
|
+
"sync did not create is never touched. Reports what would be "
|
|
46
|
+
"deleted unless --apply is also passed."
|
|
47
|
+
),
|
|
48
|
+
)
|
|
36
49
|
parser.add_argument(
|
|
37
50
|
"--apply",
|
|
38
51
|
action="store_true",
|
|
39
|
-
help=
|
|
52
|
+
help=(
|
|
53
|
+
"With --prune-orphans or --prune-uncovered, actually delete "
|
|
54
|
+
"instead of dry-run."
|
|
55
|
+
),
|
|
40
56
|
)
|
|
41
57
|
parser.add_argument(
|
|
42
58
|
"--include-quarantined",
|
|
@@ -172,6 +188,46 @@ class Command(BaseCommand):
|
|
|
172
188
|
"Dry run: re-run with --apply to delete these devices."
|
|
173
189
|
)
|
|
174
190
|
|
|
191
|
+
if options["prune_uncovered"]:
|
|
192
|
+
owned_absence = (report.get("unmanaged") or {}).get("owned_absence") or {}
|
|
193
|
+
candidates = int(owned_absence.get("absent_from_snapshot") or 0)
|
|
194
|
+
payload["uncovered_prune_requested"] = True
|
|
195
|
+
payload["uncovered_prune_applied"] = False
|
|
196
|
+
payload["uncovered_prune_candidate_count"] = candidates
|
|
197
|
+
if not owned_absence.get("available", False):
|
|
198
|
+
# Without the census nothing is known about WHY these devices
|
|
199
|
+
# are uncovered, and "absent" is the only eligible reason.
|
|
200
|
+
payload["uncovered_prune_aborted"] = "census-unavailable"
|
|
201
|
+
payload["uncovered_prune_abort_reason"] = (
|
|
202
|
+
"The Forward census that says whether each uncovered device "
|
|
203
|
+
"is absent or merely untagged did not run, so eligibility "
|
|
204
|
+
"cannot be established. Retry once Forward is reachable."
|
|
205
|
+
)
|
|
206
|
+
self.stdout.write(json.dumps(payload, indent=2, default=str))
|
|
207
|
+
raise SystemExit(2)
|
|
208
|
+
if options["apply"] and candidates:
|
|
209
|
+
result = prune_uncovered_devices(
|
|
210
|
+
sync,
|
|
211
|
+
report=report,
|
|
212
|
+
allow_scope_shrink=options["allow_scope_shrink"],
|
|
213
|
+
include_quarantined=options["include_quarantined"],
|
|
214
|
+
)
|
|
215
|
+
payload["uncovered_prune_applied"] = True
|
|
216
|
+
payload["uncovered_pruned_device_count"] = result["pruned_device_count"]
|
|
217
|
+
payload["uncovered_pruned_object_count"] = result["pruned_object_count"]
|
|
218
|
+
payload["uncovered_quarantine_held_device_count"] = result[
|
|
219
|
+
"quarantine_held_device_count"
|
|
220
|
+
]
|
|
221
|
+
payload["uncovered_quarantine_overridden_device_count"] = result[
|
|
222
|
+
"quarantine_overridden_device_count"
|
|
223
|
+
]
|
|
224
|
+
elif candidates:
|
|
225
|
+
payload["uncovered_prune_dry_run_note"] = (
|
|
226
|
+
"Dry run: re-run with --apply to delete these devices. Only "
|
|
227
|
+
f"the {candidates} absent from Forward are eligible; "
|
|
228
|
+
"uncovered devices Forward still reports are not."
|
|
229
|
+
)
|
|
230
|
+
|
|
175
231
|
self.stdout.write(json.dumps(payload, indent=2, default=str))
|
|
176
232
|
|
|
177
233
|
if options["fail_on_drift"] and out_of_scope:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Profile real branch merges with anonymized production-shaped fixtures."""
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import hashlib
|
|
4
5
|
import json
|
|
5
6
|
import logging
|
|
@@ -25,6 +26,40 @@ from forward_netbox.models import ForwardSync
|
|
|
25
26
|
from forward_netbox.utilities.merge import merge_branch
|
|
26
27
|
from forward_netbox.utilities.merge_observability import begin_merge_attempt
|
|
27
28
|
from forward_netbox.utilities.merge_profiling import MergeProfileRecorder
|
|
29
|
+
from forward_netbox.utilities.merge_profiling import profile_scope
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@contextlib.contextmanager
|
|
33
|
+
def _changediff_save_measured():
|
|
34
|
+
"""Attribute `ChangeDiff.save()` to its own scope for the merge.
|
|
35
|
+
|
|
36
|
+
The per-row cost sits inside upstream's `ObjectChange.apply`, which the
|
|
37
|
+
recorder already times as one opaque `objectchange_apply` bucket - so
|
|
38
|
+
"is branching 1.1.3's ChangeDiff saving measurable on a real merge"
|
|
39
|
+
could not be answered from a profile. Wrapping the method here rather
|
|
40
|
+
than in the merge path keeps production untouched: this is a profiling
|
|
41
|
+
command and the instrumentation belongs with the measurement.
|
|
42
|
+
"""
|
|
43
|
+
from netbox_branching.models import ChangeDiff
|
|
44
|
+
|
|
45
|
+
original = ChangeDiff.save
|
|
46
|
+
calls = {"installed": True, "count": 0}
|
|
47
|
+
|
|
48
|
+
def measured(self, *args, **kwargs):
|
|
49
|
+
calls["count"] += 1
|
|
50
|
+
with profile_scope(
|
|
51
|
+
"changediff_save",
|
|
52
|
+
owner="upstream_netbox_branching",
|
|
53
|
+
rows=1,
|
|
54
|
+
):
|
|
55
|
+
return original(self, *args, **kwargs)
|
|
56
|
+
|
|
57
|
+
ChangeDiff.save = measured
|
|
58
|
+
try:
|
|
59
|
+
yield calls
|
|
60
|
+
finally:
|
|
61
|
+
ChangeDiff.save = original
|
|
62
|
+
|
|
28
63
|
|
|
29
64
|
LOG = logging.getLogger("forward_netbox.merge_profile")
|
|
30
65
|
STAGE_CHUNK_SIZE = 500
|
|
@@ -40,6 +75,11 @@ FIXTURE_WEIGHTS = (
|
|
|
40
75
|
("ipam.ipaddress", 0.053),
|
|
41
76
|
("ipam.prefix", 0.036),
|
|
42
77
|
("dcim.site", 0.001),
|
|
78
|
+
# A tree model, so `_is_bulk_safe` (bulk_merge.py) always refuses it and
|
|
79
|
+
# the row goes through the per-object upstream fallback - the only path
|
|
80
|
+
# that reaches ChangeDiff.save(). Without it this fixture is CREATE-only
|
|
81
|
+
# and bulk-safe, so it measured everything EXCEPT the cost in question.
|
|
82
|
+
("dcim.region", 0.002),
|
|
43
83
|
)
|
|
44
84
|
|
|
45
85
|
|
|
@@ -47,6 +87,7 @@ def _allocated_counts(volume):
|
|
|
47
87
|
counts = {model: int(volume * weight) for model, weight in FIXTURE_WEIGHTS}
|
|
48
88
|
counts["dcim.device"] = max(1, counts["dcim.device"])
|
|
49
89
|
counts["dcim.site"] = max(1, counts["dcim.site"])
|
|
90
|
+
counts["dcim.region"] = max(1, counts["dcim.region"])
|
|
50
91
|
assigned = sum(counts.values())
|
|
51
92
|
counts["dcim.interface"] += volume - assigned
|
|
52
93
|
return counts
|
|
@@ -156,6 +197,7 @@ class Command(BaseCommand):
|
|
|
156
197
|
InventoryItem,
|
|
157
198
|
MACAddress,
|
|
158
199
|
Manufacturer,
|
|
200
|
+
Region,
|
|
159
201
|
Site,
|
|
160
202
|
)
|
|
161
203
|
from ipam.models import IPAddress, Prefix
|
|
@@ -267,6 +309,16 @@ class Command(BaseCommand):
|
|
|
267
309
|
),
|
|
268
310
|
)
|
|
269
311
|
|
|
312
|
+
self._stage_rows(
|
|
313
|
+
branch,
|
|
314
|
+
request,
|
|
315
|
+
counts["dcim.region"],
|
|
316
|
+
lambda index: Region.objects.create(
|
|
317
|
+
name=f"Profile Branch Region {short} {index}",
|
|
318
|
+
slug=f"profile-branch-region-{short}-{index}",
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
|
|
270
322
|
Branch.objects.filter(pk=branch.pk).update(status=BranchStatusChoices.READY)
|
|
271
323
|
branch.refresh_from_db()
|
|
272
324
|
staged_changes = branch.get_unmerged_changes().count()
|
|
@@ -296,7 +348,7 @@ class Command(BaseCommand):
|
|
|
296
348
|
"staged_changes": staged_changes,
|
|
297
349
|
}
|
|
298
350
|
)
|
|
299
|
-
with recorder.activate():
|
|
351
|
+
with recorder.activate(), _changediff_save_measured() as changediff_calls:
|
|
300
352
|
merge_branch(
|
|
301
353
|
ingestion,
|
|
302
354
|
user=user,
|
|
@@ -304,6 +356,10 @@ class Command(BaseCommand):
|
|
|
304
356
|
)
|
|
305
357
|
after_postgres = _postgres_snapshot()
|
|
306
358
|
result = recorder.result()
|
|
359
|
+
# Recorded explicitly: an absent `changediff_save` bucket otherwise
|
|
360
|
+
# cannot be told apart from instrumentation that never installed.
|
|
361
|
+
result["changediff_save_calls"] = changediff_calls["count"]
|
|
362
|
+
result["changediff_save_instrumented"] = changediff_calls["installed"]
|
|
307
363
|
result["postgres"] = _snapshot_delta(before_postgres, after_postgres)
|
|
308
364
|
result["changes_per_second"] = (
|
|
309
365
|
volume / result["wall_seconds"] if result["wall_seconds"] else 0.0
|
|
@@ -38,6 +38,15 @@ ingestion = PluginMenuItem(
|
|
|
38
38
|
permissions=["forward_netbox.view_forwardingestion"],
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
+
# The route existed and nothing reached it: issues were visible only through
|
|
42
|
+
# the ingestion that produced them, so "which failures are blocking anything,
|
|
43
|
+
# anywhere" had no page at all and was answered by a command.
|
|
44
|
+
ingestion_issue = PluginMenuItem(
|
|
45
|
+
link="plugins:forward_netbox:forwardingestionissue_list",
|
|
46
|
+
link_text=_("Ingestion Issues"),
|
|
47
|
+
permissions=["forward_netbox.view_forwardingestionissue"],
|
|
48
|
+
)
|
|
49
|
+
|
|
41
50
|
validation_run = PluginMenuItem(
|
|
42
51
|
link="plugins:forward_netbox:forwardvalidationrun_list",
|
|
43
52
|
link_text=_("Validation Runs"),
|
|
@@ -82,7 +91,7 @@ menu = PluginMenu(
|
|
|
82
91
|
label="Forward",
|
|
83
92
|
icon_class="mdi mdi-cloud-sync",
|
|
84
93
|
groups=(
|
|
85
|
-
("Data Sync", (source, sync, ingestion, validation_run)),
|
|
94
|
+
("Data Sync", (source, sync, ingestion, ingestion_issue, validation_run)),
|
|
86
95
|
("Analysis", (device_analysis,)),
|
|
87
96
|
("Configuration", (nqe_map, drift_policy)),
|
|
88
97
|
),
|