Skip to content

Commit c47eaf5

Browse files
committed
Refactor the code
Signed-off-by: Jiaxin Shan <[email protected]>
1 parent 6eb8dc1 commit c47eaf5

16 files changed

+935
-171
lines changed

pkg/constants/kvcache.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
Copyright 2025 The Aibrix Team.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
117
package constants
218

319
const (
@@ -13,12 +29,16 @@ const (
1329

1430
KVCacheAnnotationNodeAffinityDefaultKey = "machine.cluster.vke.volcengine.com/gpu-name"
1531

16-
// Deprecated: use kvcache backend directly.
17-
// distributed, centralized.
32+
// This config will be deprecated in future, users should specify kvcache backend directly.
1833
KVCacheAnnotationMode = "kvcache.orchestration.aibrix.ai/mode"
1934
KVCacheAnnotationContainerRegistry = "kvcache.orchestration.aibrix.ai/container-registry"
2035

2136
KVCacheLabelValueRoleCache = "cache"
2237
KVCacheLabelValueRoleMetadata = "metadata"
2338
KVCacheLabelValueRoleKVWatcher = "kvwatcher"
39+
40+
KVCacheBackendVineyard = "vineyard"
41+
KVCacheBackendHPKV = "hpkv"
42+
KVCacheBackendInfinistore = "infinistore"
43+
KVCacheBackendDefault = KVCacheBackendVineyard
2444
)

pkg/controller/kvcache/backends/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package backends
1818

1919
import (
2020
"fmt"
21+
2122
orchestrationv1alpha1 "github.com/vllm-project/aibrix/api/orchestration/v1alpha1"
2223
"github.com/vllm-project/aibrix/pkg/constants"
2324
corev1 "k8s.io/api/core/v1"

pkg/controller/kvcache/backends/common_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ package backends
1818

1919
import (
2020
"fmt"
21+
"testing"
22+
2123
"github.com/stretchr/testify/assert"
2224
"github.com/stretchr/testify/require"
2325
orchestrationv1alpha1 "github.com/vllm-project/aibrix/api/orchestration/v1alpha1"
2426
"github.com/vllm-project/aibrix/pkg/constants"
2527
corev1 "k8s.io/api/core/v1"
2628
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2729
"k8s.io/apimachinery/pkg/util/intstr"
28-
"testing"
2930
)
3031

3132
func TestBuildRedisPod(t *testing.T) {

pkg/controller/kvcache/backends/distributed.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
2324
orchestrationv1alpha1 "github.com/vllm-project/aibrix/api/orchestration/v1alpha1"
25+
"github.com/vllm-project/aibrix/pkg/constants"
2426
"k8s.io/klog/v2"
2527
ctrl "sigs.k8s.io/controller-runtime"
2628
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -39,9 +41,9 @@ func NewDistributedReconciler(c client.Client, backend string) *DistributedRecon
3941
BaseReconciler: &BaseReconciler{Client: c},
4042
}
4143

42-
if backend == KVCacheBackendInfinistore {
44+
if backend == constants.KVCacheBackendInfinistore {
4345
reconciler.Backend = InfiniStoreBackend{}
44-
} else if backend == KVCacheBackendHPKV {
46+
} else if backend == constants.KVCacheBackendHPKV {
4547
reconciler.Backend = HpKVBackend{}
4648
} else {
4749
panic(fmt.Sprintf("unsupported backend: %s", backend))
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
Copyright 2024 The Aibrix Team.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package backends
18+
19+
import (
20+
"context"
21+
"testing"
22+
23+
appsv1 "k8s.io/api/apps/v1"
24+
corev1 "k8s.io/api/core/v1"
25+
26+
"github.com/stretchr/testify/assert"
27+
"github.com/vllm-project/aibrix/api/orchestration/v1alpha1"
28+
"github.com/vllm-project/aibrix/pkg/constants"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
32+
)
33+
34+
// -- Test for NewDistributedReconciler --
35+
36+
func TestNewDistributedReconciler(t *testing.T) {
37+
c := fake.NewClientBuilder().Build()
38+
39+
t.Run("infinistore backend", func(t *testing.T) {
40+
reconciler := NewDistributedReconciler(c, constants.KVCacheBackendInfinistore)
41+
assert.Equal(t, "infinistore", reconciler.Backend.Name())
42+
})
43+
44+
t.Run("hpkv backend", func(t *testing.T) {
45+
reconciler := NewDistributedReconciler(c, constants.KVCacheBackendHPKV)
46+
assert.Equal(t, "hpkv", reconciler.Backend.Name())
47+
})
48+
49+
t.Run("invalid backend panics", func(t *testing.T) {
50+
assert.Panics(t, func() {
51+
NewDistributedReconciler(c, "invalid-backend")
52+
})
53+
})
54+
}
55+
56+
// -- Test for reconcileMetadataService --
57+
58+
func TestReconcileMetadataService(t *testing.T) {
59+
scheme := runtime.NewScheme()
60+
_ = corev1.AddToScheme(scheme)
61+
c := fake.NewClientBuilder().WithScheme(scheme).Build()
62+
r := NewDistributedReconciler(c, constants.KVCacheBackendInfinistore)
63+
64+
t.Run("fails when neither etcd nor redis configured", func(t *testing.T) {
65+
kv := &v1alpha1.KVCache{
66+
Spec: v1alpha1.KVCacheSpec{
67+
Metadata: &v1alpha1.MetadataConfig{},
68+
},
69+
}
70+
err := r.reconcileMetadataService(context.Background(), kv)
71+
assert.Error(t, err)
72+
assert.Contains(t, err.Error(), "either etcd or redis configuration is required")
73+
})
74+
75+
t.Run("succeeds when redis is configured", func(t *testing.T) {
76+
kv := &v1alpha1.KVCache{
77+
ObjectMeta: metav1.ObjectMeta{
78+
Name: "redis-test",
79+
Namespace: "default",
80+
},
81+
Spec: v1alpha1.KVCacheSpec{
82+
Metadata: &v1alpha1.MetadataConfig{
83+
Redis: &v1alpha1.RedisConfig{
84+
Replicas: 1,
85+
},
86+
},
87+
},
88+
}
89+
90+
// Replace backend with mock
91+
r.Backend = mockBackend{
92+
redis: &corev1.Pod{
93+
ObjectMeta: metav1.ObjectMeta{
94+
Name: "mock-redis",
95+
Namespace: "default",
96+
},
97+
},
98+
svc: &corev1.Service{
99+
ObjectMeta: metav1.ObjectMeta{
100+
Name: "mock-redis-service",
101+
Namespace: "default",
102+
},
103+
Spec: corev1.ServiceSpec{
104+
Ports: []corev1.ServicePort{
105+
{
106+
Name: "redis",
107+
Port: 6379,
108+
Protocol: corev1.ProtocolTCP,
109+
},
110+
},
111+
},
112+
},
113+
}
114+
115+
err := r.reconcileMetadataService(context.Background(), kv)
116+
assert.NoError(t, err)
117+
})
118+
}
119+
120+
// -- Test for reconcileRedisService (partial stubbed) --
121+
122+
func TestReconcileRedisService_SingleReplicaAllowed(t *testing.T) {
123+
c := fake.NewClientBuilder().Build()
124+
r := NewDistributedReconciler(c, constants.KVCacheBackendInfinistore)
125+
126+
kv := &v1alpha1.KVCache{
127+
ObjectMeta: metav1.ObjectMeta{
128+
Name: "redis-test",
129+
Namespace: "default",
130+
},
131+
Spec: v1alpha1.KVCacheSpec{
132+
Metadata: &v1alpha1.MetadataConfig{
133+
Redis: &v1alpha1.RedisConfig{
134+
Replicas: 1,
135+
},
136+
},
137+
},
138+
}
139+
140+
// Stub backend logic
141+
r.Backend = mockBackend{
142+
// dummy objects
143+
redis: &corev1.Pod{
144+
ObjectMeta: metav1.ObjectMeta{
145+
Name: "mock-redis",
146+
Namespace: "default",
147+
},
148+
},
149+
svc: &corev1.Service{
150+
ObjectMeta: metav1.ObjectMeta{
151+
Name: "mock-redis-service",
152+
Namespace: "default",
153+
},
154+
Spec: corev1.ServiceSpec{
155+
Ports: []corev1.ServicePort{
156+
{
157+
Name: "redis",
158+
Port: 6379,
159+
Protocol: corev1.ProtocolTCP,
160+
},
161+
},
162+
},
163+
},
164+
}
165+
166+
err := r.ReconcilePodObject(context.Background(), r.Backend.BuildMetadataPod(kv))
167+
assert.NoError(t, err)
168+
169+
err = r.reconcileRedisService(context.Background(), kv)
170+
assert.NoError(t, err)
171+
}
172+
173+
// -- Mock Backend for isolation --
174+
175+
type mockBackend struct {
176+
redis *corev1.Pod
177+
watcher *corev1.Pod
178+
svc *corev1.Service
179+
sts *appsv1.StatefulSet
180+
}
181+
182+
func (m mockBackend) Name() string {
183+
return "mock-backend"
184+
}
185+
186+
func (m mockBackend) ValidateObject(*v1alpha1.KVCache) error {
187+
return nil
188+
}
189+
190+
func (m mockBackend) BuildMetadataPod(*v1alpha1.KVCache) *corev1.Pod {
191+
return m.redis
192+
}
193+
194+
func (m mockBackend) BuildMetadataService(*v1alpha1.KVCache) *corev1.Service {
195+
return m.svc
196+
}
197+
198+
func (m mockBackend) BuildWatcherPod(*v1alpha1.KVCache) *corev1.Pod {
199+
return m.watcher
200+
}
201+
202+
func (m mockBackend) BuildCacheStatefulSet(*v1alpha1.KVCache) *appsv1.StatefulSet {
203+
return m.sts
204+
}
205+
206+
func (m mockBackend) BuildService(*v1alpha1.KVCache) *corev1.Service {
207+
return m.svc
208+
}

0 commit comments

Comments
 (0)