towerops/lib/towerops/snmp/interface.ex
Graham McIntire 3913981094
feat: add backhaul capacity analysis foundation
Add per-interface capacity tracking with manual override and automatic
detection from SNMP sensors. Includes capacity resolver with priority
cascade (manual > sensor > if_speed), throughput calculation from
interface stats, and site/organization-level capacity summaries.

- Migration: add configured_capacity_bps and capacity_source to snmp_interfaces
- Capacity.Resolver: multi-source capacity detection with vendor unit normalization
- Capacity context: throughput calculation, utilization percentages, site/org summaries
- Snmp context: set_manual_capacity/2 and clear_manual_capacity/1 functions
2026-03-06 17:54:32 -06:00

88 lines
2.6 KiB
Elixir

defmodule Towerops.Snmp.Interface do
@moduledoc """
SNMP interface schema for network interfaces discovered via IF-MIB.
Stores interface details (name, description, type, speed, MAC address)
and has many interface statistics.
"""
use Ecto.Schema
import Ecto.Changeset
alias Ecto.Association.NotLoaded
alias Towerops.Snmp.Device
alias Towerops.Snmp.InterfaceStat
alias Towerops.Snmp.IpAddress
alias Towerops.Snmp.Neighbor
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "snmp_interfaces" do
field :if_index, :integer
field :if_name, :string
field :if_descr, :string
field :if_alias, :string
field :if_type, :integer
field :if_speed, :integer
field :if_phys_address, :string
field :if_admin_status, :string
field :if_oper_status, :string
field :monitored, :boolean, default: true
field :configured_capacity_bps, :integer
field :capacity_source, :string
belongs_to :snmp_device, Device
has_many :stats, InterfaceStat, foreign_key: :interface_id
has_many :neighbors, Neighbor, foreign_key: :interface_id
has_many :ip_addresses, IpAddress, foreign_key: :snmp_interface_id
timestamps(type: :utc_datetime)
end
@type t :: %__MODULE__{
id: Ecto.UUID.t(),
if_index: integer(),
if_name: String.t() | nil,
if_descr: String.t() | nil,
if_alias: String.t() | nil,
if_type: integer() | nil,
if_speed: integer() | nil,
if_phys_address: String.t() | nil,
if_admin_status: String.t() | nil,
if_oper_status: String.t() | nil,
monitored: boolean(),
configured_capacity_bps: integer() | nil,
capacity_source: String.t() | nil,
snmp_device_id: Ecto.UUID.t(),
snmp_device: NotLoaded.t() | Device.t(),
stats: NotLoaded.t() | [InterfaceStat.t()],
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}
@doc false
def changeset(interface, attrs) do
interface
|> cast(attrs, [
:snmp_device_id,
:if_index,
:if_name,
:if_descr,
:if_alias,
:if_type,
:if_speed,
:if_phys_address,
:if_admin_status,
:if_oper_status,
:monitored,
:configured_capacity_bps,
:capacity_source
])
|> validate_required([:snmp_device_id, :if_index])
|> validate_inclusion(:capacity_source, ~w(manual sensor if_speed peak))
|> validate_number(:configured_capacity_bps, greater_than: 0)
|> unique_constraint([:snmp_device_id, :if_index])
|> foreign_key_constraint(:snmp_device_id)
end
end