现象:`go test ./services/` 在 HEAD(3a8eb52)上稳定失败
--- FAIL: TestGeneratedLokiConfigKeepsRequiredSettingsWithoutDefaults
deployment_config_test.go:68: required setting "delete_request_store: s3" missing
根因不是代码 bug,而是**两个测试对同一件事的期望相反**:
backend/services/deployment_config_test.go:66 断言 delete_request_store: s3
backend/services/loki_object_store_test.go:44 断言 delete_request_store: filesystem
而 loki_config.go 对 s3/oss/minio/cos/obs 是**刻意**用 filesystem 的
(commit 9f7c522 引入,注释:「删除请求存本地盘,避免各云 S3 兼容实现差异
导致 compactor 启动失败」)。
即:代码是有意的,loki_object_store_test 也是对的,只有 deployment_config_test
是历史遗留的过时断言。本次把它改成 filesystem,并加注释说明为什么不能再改回 s3,
避免以后有人"顺手修回去"。
运行时行为完全没有变化(只改测试文件),但修复后 `go test ./...` 才能全绿,
否则每次发版预检都会被这个假失败挡住。
验证:go test ./... 全部通过(services/models/repositories/pkg 均 ok)。
143 lines
5.8 KiB
Go
143 lines
5.8 KiB
Go
package services
|
||
|
||
import (
|
||
"context"
|
||
"encoding/base64"
|
||
"io"
|
||
"regexp"
|
||
"strings"
|
||
"testing"
|
||
|
||
"gopkg.in/yaml.v3"
|
||
"lokistack/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)
|
||
}
|
||
// 注意 delete_request_store 用的是 filesystem 而不是 s3:
|
||
// loki_config.go 里对 s3/oss/minio/cos/obs 刻意把 compactor 的删除请求存到
|
||
// 本地盘(见该处注释),避免各云 S3 兼容实现差异导致 compactor 启动失败。
|
||
// loki_object_store_test.go 断言的也是 filesystem —— 两处必须一致。
|
||
for _, required := range []string{"retention_period: 2160h", "retention_enabled: true", "delete_request_store: filesystem", "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)
|
||
}
|
||
}
|