- Create snmp_physical_entities table with migration - Add PhysicalEntity schema with entity_class validation - Support parent/child hierarchy via parent_entity_id - Implement discover_physical_entities/1 in Base profile - Discover chassis, modules, PSUs, fans from ENTITY-MIB - Map entity class integers to string names - Add 10 schema tests and 4 discovery tests - Fix flaky AlertNotifier test with unique names and mailbox clearing
92 lines
2.8 KiB
Elixir
92 lines
2.8 KiB
Elixir
defmodule Towerops.Snmp.PhysicalEntity do
|
|
@moduledoc """
|
|
SNMP physical entity schema for tracking hardware inventory.
|
|
|
|
Physical entities are discovered via ENTITY-MIB entPhysicalTable and include:
|
|
- Chassis (main enclosure)
|
|
- Modules (line cards, supervisor modules)
|
|
- Power supplies
|
|
- Fans
|
|
- Sensors
|
|
- Ports
|
|
|
|
Entities form a parent/child hierarchy via contained_in relationships.
|
|
"""
|
|
use Ecto.Schema
|
|
|
|
import Ecto.Changeset
|
|
|
|
alias Ecto.Association.NotLoaded
|
|
alias Towerops.Snmp.Device
|
|
|
|
@valid_entity_classes ~w(other unknown chassis backplane container power fan sensor module port stack cpu memory)
|
|
|
|
@primary_key {:id, :binary_id, autogenerate: true}
|
|
@foreign_key_type :binary_id
|
|
schema "snmp_physical_entities" do
|
|
field :entity_index, :integer
|
|
field :entity_class, :string
|
|
field :description, :string
|
|
field :model_name, :string
|
|
field :serial_number, :string
|
|
field :firmware_revision, :string
|
|
field :hardware_revision, :string
|
|
field :manufacturer_name, :string
|
|
field :position, :integer
|
|
field :last_checked_at, :utc_datetime
|
|
field :metadata, :map, default: %{}
|
|
|
|
belongs_to :snmp_device, Device
|
|
belongs_to :parent_entity, __MODULE__
|
|
has_many :child_entities, __MODULE__, foreign_key: :parent_entity_id
|
|
|
|
timestamps(type: :utc_datetime)
|
|
end
|
|
|
|
@type t :: %__MODULE__{
|
|
id: Ecto.UUID.t(),
|
|
entity_index: integer(),
|
|
entity_class: String.t(),
|
|
description: String.t() | nil,
|
|
model_name: String.t() | nil,
|
|
serial_number: String.t() | nil,
|
|
firmware_revision: String.t() | nil,
|
|
hardware_revision: String.t() | nil,
|
|
manufacturer_name: String.t() | nil,
|
|
position: integer() | nil,
|
|
last_checked_at: DateTime.t() | nil,
|
|
metadata: map(),
|
|
snmp_device_id: Ecto.UUID.t(),
|
|
snmp_device: NotLoaded.t() | Device.t(),
|
|
parent_entity_id: Ecto.UUID.t() | nil,
|
|
parent_entity: NotLoaded.t() | t() | nil,
|
|
child_entities: NotLoaded.t() | [t()],
|
|
inserted_at: DateTime.t(),
|
|
updated_at: DateTime.t()
|
|
}
|
|
|
|
@doc false
|
|
def changeset(physical_entity, attrs) do
|
|
physical_entity
|
|
|> cast(attrs, [
|
|
:snmp_device_id,
|
|
:entity_index,
|
|
:entity_class,
|
|
:description,
|
|
:model_name,
|
|
:serial_number,
|
|
:firmware_revision,
|
|
:hardware_revision,
|
|
:manufacturer_name,
|
|
:position,
|
|
:parent_entity_id,
|
|
:last_checked_at,
|
|
:metadata
|
|
])
|
|
|> validate_required([:snmp_device_id, :entity_index, :entity_class])
|
|
|> validate_inclusion(:entity_class, @valid_entity_classes)
|
|
|> unique_constraint([:snmp_device_id, :entity_index])
|
|
|> foreign_key_constraint(:snmp_device_id)
|
|
|> foreign_key_constraint(:parent_entity_id)
|
|
end
|
|
end
|