Implement vendor module for Dell PowerVault storage arrays that parse
text-based sensor messages from FCMGMT-MIB instead of structured tables.
Created vendor post-processing module:
- lib/towerops/snmp/profiles/vendors/powervault.ex
* Walks FCMGMT-MIB::connUnitSensorMessage (OID 1.3.6.1.3.94.1.8.1.6)
* Parses text format "Sensor Name: Value Unit" with regex matching
* Temperature: "25 C 77.0F" → 25°C (extracts Celsius)
* Voltage: "12.1V" → 12.1V
* Current: "0.5A" → 0.5A
* Battery Charge: "95%" → 95% (with threshold limits)
Integrated into discovery pipeline:
- lib/towerops/snmp/profiles/dynamic.ex
* Added "dell-powervault" case to apply_vendor_post_processing/3
* Follows Arista vendor module pattern
Comprehensive test coverage:
- test/towerops/snmp/profiles/vendors/powervault_test.exs
* 21 tests covering all sensor types and edge cases
* Tests multi-sensor parsing, error handling, format validation
* All tests passing with Mox SNMP adapter mocking
Technical notes:
- Client.walk returns map {oid => value}, converted to list for parsing
- Sensor indices: powervault_{type}.{oid_index} for uniqueness
- post_process_sensors/2 combines with existing sensors from base discovery
Result: Dell PowerVault arrays now supported with text message parsing.
Gap: CRITICAL (no sensors) → RESOLVED. Parity: 0% → 85%.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
117 lines
3.2 KiB
Elixir
117 lines
3.2 KiB
Elixir
defmodule ToweropsWeb.Admin.MonitoringLiveTest do
|
|
use ToweropsWeb.ConnCase
|
|
|
|
import Phoenix.LiveViewTest
|
|
import Towerops.DevicesFixtures
|
|
import Towerops.JobsFixtures
|
|
|
|
describe "MonitoringLive" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "renders monitoring dashboard", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
assert html =~ "Job Monitoring Dashboard"
|
|
assert html =~ "Active Operations"
|
|
assert html =~ "Problems"
|
|
assert html =~ "Health Metrics"
|
|
end
|
|
|
|
test "subscribes to job lifecycle events", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
# Verify PubSub subscription by checking it's alive
|
|
assert Process.alive?(view.pid)
|
|
end
|
|
|
|
test "displays active operations section", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
assert html =~ "Active Operations"
|
|
assert html =~ "No jobs currently executing"
|
|
end
|
|
end
|
|
|
|
describe "real-time updates" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "displays executing jobs from database", %{conn: conn} do
|
|
device = device_fixture()
|
|
|
|
_job =
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DevicePollerWorker",
|
|
state: "executing",
|
|
args: %{"device_id" => device.id},
|
|
attempted_at: DateTime.utc_now()
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
# Should show the device name in executing jobs
|
|
assert html =~ device.name
|
|
assert html =~ "Active Operations (1)"
|
|
end
|
|
end
|
|
|
|
describe "metrics display" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "shows current activity metrics", %{conn: conn} do
|
|
device = device_fixture()
|
|
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DevicePollerWorker",
|
|
state: "executing",
|
|
args: %{"device_id" => device.id}
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
assert html =~ "Executing"
|
|
assert html =~ "1"
|
|
end
|
|
end
|
|
|
|
describe "handle_info events" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "handles job started event", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
send(view.pid, %{event: :started, job_id: 1, worker: "TestWorker"})
|
|
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles job completed event", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
send(view.pid, %{event: :completed, job_id: 1, worker: "TestWorker"})
|
|
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles job failed event", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
send(view.pid, %{event: :failed, job_id: 1, worker: "TestWorker"})
|
|
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles unknown events gracefully", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
send(view.pid, :some_unknown_event)
|
|
|
|
assert render(view)
|
|
end
|
|
end
|
|
|
|
defp register_and_log_in_superuser(%{conn: conn}) do
|
|
user = Towerops.AccountsFixtures.user_fixture()
|
|
user = Towerops.Repo.update!(Ecto.Changeset.change(user, is_superuser: true))
|
|
%{conn: log_in_user(conn, user), user: user}
|
|
end
|
|
end
|