- Added DM Sans font to the project, including multiple weights and styles for improved typography. - Updated package.json and package-lock.json to include @fontsource/dm-sans dependency. - Enhanced dashboard context to include current cluster CPU state, integrating new data into the context and API responses. - Updated relevant templates and JavaScript to utilize the new current cluster data for better visualization and user experience.
262 lines
11 KiB
Python
262 lines
11 KiB
Python
"""Tests for dashboard.views."""
|
|
import json
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from django.test import TestCase, RequestFactory
|
|
from django.core.cache import cache
|
|
|
|
from dashboard.views import (
|
|
index,
|
|
collect_context,
|
|
collect_stats,
|
|
collect_audits,
|
|
api_stats,
|
|
api_audits,
|
|
)
|
|
|
|
|
|
def _minimal_render_context(region_name="test", first_flavor_name="f1", vm_count=1):
|
|
"""Context with all keys the index.html template expects."""
|
|
return {
|
|
"region": {"name": region_name, "hosts_total": 1},
|
|
"pcpu": {"total": 1, "usage": 0, "free": 1, "used_percentage": 0},
|
|
"vcpu": {"total": 2, "allocated": 1, "free": 1, "allocated_percentage": 50, "overcommit_ratio": 1, "overcommit_max": 2},
|
|
"pram": {"total": 1024**3, "usage": 0, "free": 1024**3, "used_percentage": 0},
|
|
"vram": {"total": 1024**3, "allocated": 0, "free": 1024**3, "allocated_percentage": 0, "overcommit_ratio": 0, "overcommit_max": 1},
|
|
"vm": {"count": vm_count, "active": vm_count, "stopped": 0, "avg_cpu": 1, "avg_ram": 0, "density": float(vm_count)},
|
|
"flavors": {
|
|
"first_common_flavor": {"name": first_flavor_name, "count": vm_count},
|
|
"second_common_flavor": {"name": "—", "count": 0},
|
|
"third_common_flavor": {"name": "—", "count": 0},
|
|
},
|
|
"audits": [],
|
|
}
|
|
|
|
|
|
class IndexViewTest(TestCase):
|
|
"""Tests for the index view."""
|
|
|
|
def setUp(self):
|
|
self.factory = RequestFactory()
|
|
|
|
@patch("dashboard.views.settings")
|
|
def test_index_use_mock_data_returns_200_and_mock_context(self, mock_settings):
|
|
mock_settings.USE_MOCK_DATA = True
|
|
mock_settings.DASHBOARD_CACHE_TTL = 120
|
|
request = self.factory.get("/")
|
|
response = index(request)
|
|
self.assertEqual(response.status_code, 200)
|
|
# Mock context contains mock-region and flavors; render uses index.html
|
|
content = response.content.decode()
|
|
self.assertIn("mock-region", content)
|
|
self.assertIn("m1.small", content)
|
|
|
|
@patch("dashboard.views.collect_context")
|
|
@patch("dashboard.views.settings")
|
|
def test_index_without_mock_returns_skeleton_and_does_not_call_collect_context(
|
|
self, mock_settings, mock_collect_context
|
|
):
|
|
mock_settings.USE_MOCK_DATA = False
|
|
request = self.factory.get("/")
|
|
response = index(request)
|
|
self.assertEqual(response.status_code, 200)
|
|
mock_collect_context.assert_not_called()
|
|
content = response.content.decode()
|
|
self.assertIn('data-dashboard="skeleton"', content)
|
|
self.assertIn("—", content)
|
|
|
|
|
|
class CollectContextTest(TestCase):
|
|
"""Tests for collect_context with mocked dependencies."""
|
|
|
|
def _make_mock_connection(self, region_name="test-region"):
|
|
conn = MagicMock()
|
|
conn._compute_region = region_name
|
|
return conn
|
|
|
|
@patch("dashboard.views.get_current_cluster_cpu")
|
|
@patch("dashboard.views._fetch_prometheus_metrics")
|
|
@patch("dashboard.views.get_audits")
|
|
@patch("dashboard.views.get_flavor_list")
|
|
@patch("dashboard.views.get_connection")
|
|
def test_collect_context_structure_and_calculation(
|
|
self, mock_get_connection, mock_get_flavor_list, mock_get_audits, mock_fetch_metrics, mock_get_current_cluster_cpu
|
|
):
|
|
mock_get_connection.return_value = self._make_mock_connection("my-region")
|
|
mock_get_current_cluster_cpu.return_value = {"host_labels": ["h0", "h1"], "cpu_current": [30.0, 40.0]}
|
|
mock_get_flavor_list.return_value = {
|
|
"first_common_flavor": {"name": "m1.small", "count": 5},
|
|
"second_common_flavor": {"name": "—", "count": 0},
|
|
"third_common_flavor": {"name": "—", "count": 0},
|
|
}
|
|
mock_get_audits.return_value = [
|
|
{
|
|
"migrations": [],
|
|
"host_labels": ["h0", "h1"],
|
|
"cpu_current": [30.0, 40.0],
|
|
"cpu_projected": [35.0, 35.0],
|
|
}
|
|
]
|
|
mock_fetch_metrics.return_value = {
|
|
"hosts_total": 2,
|
|
"pcpu_total": 8,
|
|
"pcpu_usage": 2.5,
|
|
"vcpu_allocated": 16,
|
|
"vcpu_overcommit_max": 2.0,
|
|
"pram_total": 32 * 1024**3,
|
|
"pram_usage": 8 * 1024**3,
|
|
"vram_allocated": 24 * 1024**3,
|
|
"vram_overcommit_max": 1.5,
|
|
"vm_count": 4,
|
|
"vm_active": 4,
|
|
}
|
|
|
|
context = collect_context()
|
|
|
|
self.assertEqual(context["region"]["name"], "my-region")
|
|
self.assertEqual(context["region"]["hosts_total"], 2)
|
|
self.assertEqual(context["pcpu"]["total"], 8)
|
|
self.assertEqual(context["pcpu"]["usage"], 2.5)
|
|
self.assertEqual(context["vcpu"]["total"], 8 * 2.0) # pcpu_total * vcpu_overcommit_max
|
|
self.assertEqual(context["vcpu"]["allocated"], 16)
|
|
self.assertEqual(context["vram"]["total"], 32 * 1024**3 * 1.5)
|
|
self.assertEqual(context["flavors"]["first_common_flavor"]["name"], "m1.small")
|
|
self.assertEqual(len(context["audits"]), 1)
|
|
# Serialized for JS
|
|
import json
|
|
self.assertIsInstance(context["audits"][0]["migrations"], str)
|
|
self.assertEqual(json.loads(context["audits"][0]["host_labels"]), ["h0", "h1"])
|
|
self.assertIn("current_cluster", context)
|
|
self.assertEqual(json.loads(context["current_cluster"]["host_labels"]), ["h0", "h1"])
|
|
self.assertEqual(json.loads(context["current_cluster"]["cpu_current"]), [30.0, 40.0])
|
|
|
|
|
|
class ApiStatsTest(TestCase):
|
|
"""Tests for api_stats view."""
|
|
|
|
def setUp(self):
|
|
self.factory = RequestFactory()
|
|
|
|
@patch("dashboard.views._fetch_prometheus_metrics")
|
|
@patch("dashboard.views.get_flavor_list")
|
|
@patch("dashboard.views.get_connection")
|
|
def test_api_stats_returns_json_with_expected_keys(
|
|
self, mock_get_connection, mock_get_flavor_list, mock_fetch_metrics
|
|
):
|
|
conn = MagicMock()
|
|
conn._compute_region = "api-region"
|
|
mock_get_connection.return_value = conn
|
|
mock_get_flavor_list.return_value = {
|
|
"first_common_flavor": {"name": "m1.small", "count": 3},
|
|
"second_common_flavor": {"name": "—", "count": 0},
|
|
"third_common_flavor": {"name": "—", "count": 0},
|
|
}
|
|
mock_fetch_metrics.return_value = {
|
|
"hosts_total": 2,
|
|
"pcpu_total": 4,
|
|
"pcpu_usage": 1.0,
|
|
"vcpu_allocated": 8,
|
|
"vcpu_overcommit_max": 2.0,
|
|
"pram_total": 16 * 1024**3,
|
|
"pram_usage": 4 * 1024**3,
|
|
"vram_allocated": 12 * 1024**3,
|
|
"vram_overcommit_max": 1.5,
|
|
"vm_count": 2,
|
|
"vm_active": 2,
|
|
}
|
|
cache.clear()
|
|
request = self.factory.get("/api/stats/")
|
|
with patch("dashboard.views.settings") as mock_settings:
|
|
mock_settings.DASHBOARD_CACHE_TTL = 120
|
|
response = api_stats(request)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response["Content-Type"], "application/json")
|
|
data = json.loads(response.content)
|
|
self.assertEqual(data["region"]["name"], "api-region")
|
|
self.assertEqual(data["region"]["hosts_total"], 2)
|
|
self.assertIn("pcpu", data)
|
|
self.assertIn("pram", data)
|
|
self.assertIn("vcpu", data)
|
|
self.assertIn("vram", data)
|
|
self.assertIn("vm", data)
|
|
self.assertIn("flavors", data)
|
|
self.assertEqual(data["flavors"]["first_common_flavor"]["name"], "m1.small")
|
|
|
|
@patch("dashboard.views.collect_stats")
|
|
@patch("dashboard.views.settings")
|
|
def test_api_stats_uses_cache(self, mock_settings, mock_collect_stats):
|
|
mock_settings.DASHBOARD_CACHE_TTL = 120
|
|
cached = {"region": {"name": "cached", "hosts_total": 1}, "pcpu": {}, "pram": {}, "vcpu": {}, "vram": {}, "vm": {}, "flavors": {}}
|
|
cache.clear()
|
|
cache.set("dashboard_stats", cached, timeout=120)
|
|
request = self.factory.get("/api/stats/")
|
|
response = api_stats(request)
|
|
mock_collect_stats.assert_not_called()
|
|
self.assertEqual(json.loads(response.content)["region"]["name"], "cached")
|
|
|
|
|
|
class ApiAuditsTest(TestCase):
|
|
"""Tests for api_audits view."""
|
|
|
|
def setUp(self):
|
|
self.factory = RequestFactory()
|
|
|
|
@patch("dashboard.views.get_current_cluster_cpu")
|
|
@patch("dashboard.views.get_audits")
|
|
@patch("dashboard.views.get_connection")
|
|
def test_api_audits_returns_json_audits_list(
|
|
self, mock_get_connection, mock_get_audits, mock_get_current_cluster_cpu
|
|
):
|
|
mock_get_connection.return_value = MagicMock()
|
|
mock_get_audits.return_value = [
|
|
{
|
|
"id": "audit-1",
|
|
"name": "Test Audit",
|
|
"created_at": "2025-02-01T10:00:00",
|
|
"strategy": "Balanced",
|
|
"goal": "BALANCED",
|
|
"scope": "Full Cluster",
|
|
"cpu_weight": "1.0",
|
|
"ram_weight": "1.0",
|
|
"migrations": [{"instanceName": "i1", "source": "h0", "destination": "h1", "flavor": "m1.small", "impact": "Low"}],
|
|
"host_labels": ["h0", "h1"],
|
|
"cpu_current": [30.0, 40.0],
|
|
"cpu_projected": [35.0, 35.0],
|
|
}
|
|
]
|
|
mock_get_current_cluster_cpu.return_value = {"host_labels": ["h0", "h1"], "cpu_current": [30.0, 40.0]}
|
|
cache.clear()
|
|
request = self.factory.get("/api/audits/")
|
|
with patch("dashboard.views.settings") as mock_settings:
|
|
mock_settings.DASHBOARD_CACHE_TTL = 120
|
|
response = api_audits(request)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response["Content-Type"], "application/json")
|
|
data = json.loads(response.content)
|
|
self.assertIn("audits", data)
|
|
self.assertEqual(len(data["audits"]), 1)
|
|
self.assertEqual(data["audits"][0]["name"], "Test Audit")
|
|
self.assertIsInstance(data["audits"][0]["migrations"], str)
|
|
self.assertIsInstance(data["audits"][0]["host_labels"], str)
|
|
self.assertIn("current_cluster", data)
|
|
self.assertEqual(data["current_cluster"]["host_labels"], ["h0", "h1"])
|
|
self.assertEqual(data["current_cluster"]["cpu_current"], [30.0, 40.0])
|
|
|
|
@patch("dashboard.views.get_current_cluster_cpu")
|
|
@patch("dashboard.views.collect_audits")
|
|
@patch("dashboard.views.settings")
|
|
def test_api_audits_uses_cache(self, mock_settings, mock_collect_audits, mock_get_current_cluster_cpu):
|
|
mock_settings.DASHBOARD_CACHE_TTL = 120
|
|
cached_audits = [{"id": "cached-1", "name": "Cached Audit", "migrations": "[]", "host_labels": "[]", "cpu_current": "[]", "cpu_projected": "[]"}]
|
|
cached_cluster = {"host_labels": ["cached-h0"], "cpu_current": [10.0]}
|
|
cache.clear()
|
|
cache.set("dashboard_audits", cached_audits, timeout=120)
|
|
cache.set("dashboard_current_cluster", cached_cluster, timeout=120)
|
|
request = self.factory.get("/api/audits/")
|
|
response = api_audits(request)
|
|
mock_collect_audits.assert_not_called()
|
|
mock_get_current_cluster_cpu.assert_not_called()
|
|
data = json.loads(response.content)
|
|
self.assertEqual(data["audits"][0]["name"], "Cached Audit")
|
|
self.assertEqual(data["current_cluster"], cached_cluster)
|