Skip to content

Commit 7fbb11b

Browse files
committed
feat: implement backend caching with robfig/go-cache for improved performance
1 parent aac928d commit 7fbb11b

34 files changed

+354
-132
lines changed

config/config.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@ var ConsoleConfig *Config
1616

1717
const defaultHost = "localhost"
1818

19+
// Cache TTL validation limits.
20+
const (
21+
MaxCacheTTL = 5 * time.Minute // Maximum cache TTL (5 minutes)
22+
MaxPowerStateTTL = 1 * time.Minute // Maximum power state TTL (1 minute)
23+
MinCacheTTL = 0 // Minimum (0 = disabled)
24+
)
25+
26+
// Cache validation errors.
27+
var (
28+
ErrCacheTTLNegative = errors.New("cache ttl cannot be negative")
29+
ErrCacheTTLExceedsMax = errors.New("cache ttl exceeds maximum allowed value of 5 minutes")
30+
ErrCachePowerStateTTLNegative = errors.New("cache powerstate_ttl cannot be negative")
31+
ErrCachePowerStateTTLExceedsMax = errors.New("cache powerstate_ttl exceeds maximum allowed value of 1 minute")
32+
)
33+
1934
type (
2035
// Config -.
2136
Config struct {
@@ -27,6 +42,7 @@ type (
2742
EA `yaml:"ea"`
2843
Auth `yaml:"auth"`
2944
UI `yaml:"ui"`
45+
Cache `yaml:"cache"`
3046
}
3147

3248
// App -.
@@ -110,8 +126,36 @@ type (
110126
UI struct {
111127
ExternalURL string `yaml:"externalUrl" env:"UI_EXTERNAL_URL"`
112128
}
129+
130+
// Cache -.
131+
Cache struct {
132+
TTL time.Duration `yaml:"ttl" env:"CACHE_TTL"` // Cache TTL for features/KVM (set to 0 to disable caching, max 5 minutes)
133+
PowerStateTTL time.Duration `yaml:"powerstate_ttl" env:"CACHE_POWERSTATE_TTL"` // Power state TTL (typically shorter since it changes more frequently, max 1 minute)
134+
}
113135
)
114136

137+
// ValidateCacheConfig validates cache configuration values for security.
138+
// Returns error if values are negative or exceed maximum limits.
139+
func (c *Config) ValidateCacheConfig() error {
140+
if c.Cache.TTL < MinCacheTTL {
141+
return ErrCacheTTLNegative
142+
}
143+
144+
if c.Cache.TTL > MaxCacheTTL {
145+
return ErrCacheTTLExceedsMax
146+
}
147+
148+
if c.Cache.PowerStateTTL < MinCacheTTL {
149+
return ErrCachePowerStateTTLNegative
150+
}
151+
152+
if c.Cache.PowerStateTTL > MaxPowerStateTTL {
153+
return ErrCachePowerStateTTLExceedsMax
154+
}
155+
156+
return nil
157+
}
158+
115159
// getPreferredIPAddress detects the most likely candidate IP address for this machine.
116160
// It prefers non-loopback IPv4 addresses and excludes link-local addresses.
117161
func getPreferredIPAddress() string {
@@ -197,6 +241,10 @@ func defaultConfig() *Config {
197241
UI: UI{
198242
ExternalURL: "",
199243
},
244+
Cache: Cache{
245+
TTL: 30 * time.Second,
246+
PowerStateTTL: 5 * time.Second,
247+
},
200248
}
201249
}
202250

@@ -279,5 +327,10 @@ func NewConfig() (*Config, error) {
279327
return nil, err
280328
}
281329

330+
// Validate cache configuration
331+
if err := ConsoleConfig.ValidateCacheConfig(); err != nil {
332+
return nil, err
333+
}
334+
282335
return ConsoleConfig, nil
283336
}

config/config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,13 @@ ui:
5454
# - Ignored: When building without 'noui' tag (embedded UI is served normally)
5555
# Example: https://ui.example.com
5656
externalUrl: ""
57+
cache:
58+
# ttl: Cache time-to-live for AMT features and KVM data
59+
# Valid range: 0 (disabled) to 5 minutes (300s)
60+
# Recommended: 30s for balance between performance and freshness
61+
ttl: 30s
62+
# powerstate_ttl: Separate TTL for power state (changes more frequently)
63+
# Valid range: 0 (disabled) to 1 minute (60s)
64+
# Recommended: 5s since power state changes during operations
65+
powerstate_ttl: 5s
5766

config/config_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"os"
55
"testing"
6+
"time"
67

78
"github.com/stretchr/testify/assert"
89
)
@@ -107,3 +108,85 @@ postgres:
107108
assert.Equal(t, 10, cfg.PoolMax)
108109
assert.Equal(t, "postgres://envuser:envpassword@localhost:5432/envdb", cfg.DB.URL)
109110
}
111+
112+
func TestValidateCacheConfig(t *testing.T) {
113+
t.Parallel()
114+
115+
tests := []struct {
116+
name string
117+
cache Cache
118+
expectedError string
119+
}{
120+
{
121+
name: "valid default values",
122+
cache: Cache{
123+
TTL: 30 * time.Second,
124+
PowerStateTTL: 5 * time.Second,
125+
},
126+
expectedError: "",
127+
},
128+
{
129+
name: "valid disabled cache",
130+
cache: Cache{
131+
TTL: 0,
132+
PowerStateTTL: 0,
133+
},
134+
expectedError: "",
135+
},
136+
{
137+
name: "valid maximum values",
138+
cache: Cache{
139+
TTL: MaxCacheTTL,
140+
PowerStateTTL: MaxPowerStateTTL,
141+
},
142+
expectedError: "",
143+
},
144+
{
145+
name: "negative ttl",
146+
cache: Cache{
147+
TTL: -1 * time.Second,
148+
PowerStateTTL: 5 * time.Second,
149+
},
150+
expectedError: "cache ttl cannot be negative",
151+
},
152+
{
153+
name: "negative powerstate_ttl",
154+
cache: Cache{
155+
TTL: 30 * time.Second,
156+
PowerStateTTL: -1 * time.Second,
157+
},
158+
expectedError: "cache powerstate_ttl cannot be negative",
159+
},
160+
{
161+
name: "ttl exceeds maximum",
162+
cache: Cache{
163+
TTL: 6 * time.Minute,
164+
PowerStateTTL: 5 * time.Second,
165+
},
166+
expectedError: "cache ttl exceeds maximum allowed value of 5 minutes",
167+
},
168+
{
169+
name: "powerstate_ttl exceeds maximum",
170+
cache: Cache{
171+
TTL: 30 * time.Second,
172+
PowerStateTTL: 2 * time.Minute,
173+
},
174+
expectedError: "cache powerstate_ttl exceeds maximum allowed value of 1 minute",
175+
},
176+
}
177+
178+
for _, tt := range tests {
179+
t.Run(tt.name, func(t *testing.T) {
180+
t.Parallel()
181+
182+
cfg := &Config{Cache: tt.cache}
183+
err := cfg.ValidateCacheConfig()
184+
if tt.expectedError == "" {
185+
assert.NoError(t, err)
186+
} else {
187+
assert.Error(t, err)
188+
assert.Contains(t, err.Error(), tt.expectedError)
189+
}
190+
})
191+
}
192+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/Masterminds/squirrel v1.5.4
99
github.com/coreos/go-oidc/v3 v3.17.0
1010
github.com/device-management-toolkit/go-wsman-messages/v2 v2.36.1
11+
github.com/gin-contrib/cache v1.4.1
1112
github.com/gin-contrib/cors v1.7.6
1213
github.com/gin-contrib/pprof v1.5.3
1314
github.com/gin-gonic/gin v1.11.0
@@ -29,13 +30,15 @@ require (
2930

3031
require (
3132
al.essio.dev/pkg/shellescape v1.5.1 // indirect
33+
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
3234
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
3335
github.com/danieljoos/wincred v1.2.2 // indirect
3436
github.com/getkin/kin-openapi v0.133.0 // indirect
3537
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
3638
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
3739
github.com/goccy/go-yaml v1.18.0 // indirect
3840
github.com/godbus/dbus/v5 v5.1.0 // indirect
41+
github.com/gomodule/redigo v1.9.2 // indirect
3942
github.com/gorilla/schema v1.4.1 // indirect
4043
github.com/hashicorp/errwrap v1.1.0 // indirect
4144
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -47,6 +50,7 @@ require (
4750
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
4851
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
4952
github.com/kr/text v0.2.0 // indirect
53+
github.com/memcachier/mc/v3 v3.0.3 // indirect
5054
github.com/mitchellh/go-homedir v1.1.0 // indirect
5155
github.com/mitchellh/mapstructure v1.5.0 // indirect
5256
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
@@ -56,6 +60,7 @@ require (
5660
github.com/perimeterx/marshmallow v1.1.5 // indirect
5761
github.com/quic-go/qpack v0.6.0 // indirect
5862
github.com/quic-go/quic-go v0.57.0 // indirect
63+
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
5964
github.com/ryanuber/go-glob v1.0.0 // indirect
6065
github.com/woodsbury/decimal128 v1.4.0 // indirect
6166
github.com/zalando/go-keyring v0.2.6 // indirect

0 commit comments

Comments
 (0)