Files
LokiStack/backend/services/deployment_config_test.go
T
logan 2d73661dd1 feat: complete Loki query, insights, settings and deployment workflows
Add the Loki query UI, access policies, ingress insights, alert and registry settings, and their backend APIs. Include the related Kubernetes deployment and collector updates required by the integrated application.

Validated with go test ./..., npm run build, and npm run lint.
2026-10-25 21:28:32 +08:00

139 lines
5.5 KiB
Go

package services
import (
"context"
"encoding/base64"
"io"
"regexp"
"strings"
"testing"
"gopkg.in/yaml.v3"
"opsxloki/models"
)
func TestRedactLokiYAML(t *testing.T) {
source := "storage_config:\n aws:\n bucketnames: loki\n access_key_id: ENC:private-access\n secret_access_key: private-secret\nlimits_config:\n retention_period: 720h\n"
got, err := redactLokiYAML(source)
if err != nil {
t.Fatal(err)
}
for _, secret := range []string{"private-access", "private-secret", "ENC:"} {
if strings.Contains(got, secret) {
t.Fatalf("sensitive value remains in config: %s", secret)
}
}
for _, want := range []string{"bucketnames: loki", "retention_period: 720h", "access_key_id: '********'"} {
if !strings.Contains(got, want) {
t.Fatalf("missing %q in sanitized config: %s", want, got)
}
}
}
func TestMinIOConfigUsesBucketNameNotPathPrefix(t *testing.T) {
d := NewLokiDeployer(&models.LokiDeployment{StorageConfig: &models.StorageConfig{
StorageType: "minio", Bucket: "logs-bucket", PathPrefix: "wrong-prefix", Endpoint: "http://minio:9000",
AccessKey: "key", SecretKey: "secret", Insecure: true,
}}, nil)
config := d.generateLokiConfig()
if count := strings.Count(config, "bucketnames: logs-bucket"); count != 2 {
t.Fatalf("bucket name should appear in storage and ruler config; got %d", count)
}
if strings.Contains(config, "wrong-prefix") || strings.Contains(config, "s3://") {
t.Fatalf("invalid S3 URL or path prefix in generated config")
}
}
func TestDeployRejectsUndecryptedStorageCredential(t *testing.T) {
d := NewLokiDeployer(&models.LokiDeployment{StorageConfig: &models.StorageConfig{
StorageType: "minio", AccessKey: "ENC:unavailable", SecretKey: "secret",
}}, nil)
if err := d.Deploy(context.Background()); err == nil || !strings.Contains(err.Error(), "存储凭证解密失败") {
t.Fatalf("expected credential failure, got %v", err)
}
}
func TestGeneratedLokiConfigKeepsRequiredSettingsWithoutDefaults(t *testing.T) {
d := NewLokiDeployer(&models.LokiDeployment{RetentionDays: 90, Replicas: 1, StorageConfig: &models.StorageConfig{
StorageType: "minio", Bucket: "logs", Endpoint: "http://minio:9000",
AccessKey: "key", SecretKey: "secret", Insecure: true,
}}, nil)
config := d.generateLokiConfig()
var parsed map[string]interface{}
if err := yaml.Unmarshal([]byte(config), &parsed); err != nil {
t.Fatalf("invalid Loki YAML: %v", err)
}
for _, required := range []string{"retention_period: 2160h", "retention_enabled: true", "delete_request_store: s3", "bucketnames: logs", "store: tsdb", "schema: v13"} {
if !strings.Contains(config, required) {
t.Fatalf("required setting %q missing", required)
}
}
for _, defaultSetting := range []string{"http_listen_port:", "grpc_listen_port:", "chunk_idle_period:", "compaction_interval:", "retention_delete_delay:", "max_entries_limit_per_query:", "frontend_worker:", "chunk_store_config:", "autoforget_unhealthy:"} {
if strings.Contains(config, defaultSetting) {
t.Fatalf("unnecessary setting %q remained", defaultSetting)
}
}
d.deployment.Replicas = 2
if !strings.Contains(d.generateLokiConfig(), "autoforget_unhealthy: true") {
t.Fatal("multi-replica ring recovery setting missing")
}
}
func TestK8sManifestKeepsStorageCredentialsInSecret(t *testing.T) {
d := NewLokiDeployer(&models.LokiDeployment{Mode: "k8s-yaml", RetentionDays: 60, Replicas: 1, StorageConfig: &models.StorageConfig{
StorageType: "minio", Bucket: "loki", Endpoint: "http://minio:9000",
AccessKey: "test-access", SecretKey: "test-secret", Insecure: true,
}}, nil)
manifest := d.generateManifest("log")
decoder := yaml.NewDecoder(strings.NewReader(manifest))
var configYAML string
var secret map[string]interface{}
for {
var doc map[string]interface{}
if err := decoder.Decode(&doc); err != nil {
if err == io.EOF {
break
}
t.Fatalf("invalid manifest YAML: %v", err)
}
switch doc["kind"] {
case "ConfigMap":
configYAML = doc["data"].(map[string]interface{})["loki.yaml"].(string)
case "Secret":
secret = doc["data"].(map[string]interface{})
}
}
if configYAML == "" || secret == nil {
t.Fatal("ConfigMap or Secret missing")
}
if strings.Contains(configYAML, "test-access") || strings.Contains(configYAML, "test-secret") {
t.Fatal("storage credential leaked into ConfigMap")
}
for _, marker := range []string{"${LOKI_STORAGE_ACCESS_KEY}", "${LOKI_STORAGE_SECRET_KEY}", "retention_period: 1440h", "retention_enabled: true"} {
if !strings.Contains(configYAML, marker) {
t.Fatalf("required Loki setting %q missing", marker)
}
}
if !strings.Contains(manifest, "-config.expand-env=true") || !strings.Contains(manifest, "secretKeyRef:") {
t.Fatal("StatefulSet does not reference storage Secret")
}
for key, want := range map[string]string{"accessKey": "test-access", "secretKey": "test-secret"} {
decoded, err := base64.StdEncoding.DecodeString(secret[key].(string))
if err != nil || string(decoded) != want {
t.Fatalf("Secret %s data mismatch", key)
}
}
}
func TestK8sManifestChangesPodTemplateWhenRetentionChanges(t *testing.T) {
deployment := &models.LokiDeployment{RetentionDays: 30, Replicas: 1}
d := NewLokiDeployer(deployment, nil)
checksum := regexp.MustCompile(`checksum/loki-config: "[a-f0-9]{64}"`)
first := checksum.FindString(d.generateManifest("log"))
deployment.RetentionDays = 70
second := checksum.FindString(d.generateManifest("log"))
if first == "" || second == "" || first == second {
t.Fatalf("Pod config checksum must change with retention: %q -> %q", first, second)
}
}