This is an automated email from the ASF dual-hosted git repository.
alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git
The following commit(s) were added to refs/heads/develop by this push:
new 90fdfc2b feat: support memory type of store (#1332)
90fdfc2b is described below
commit 90fdfc2b25602379cf7c17314a1edc1a355da682
Author: robb <[email protected]>
AuthorDate: Sun Oct 12 11:28:15 2025 +0800
feat: support memory type of store (#1332)
* feat: support memory type of store
* fix: muilti indexes should use intersecection
* simplify code
Co-authored-by: Copilot <[email protected]>
* refractor: GetByKeys return a list instead of map
---------
Co-authored-by: Copilot <[email protected]>
---
app/dubbo-admin/dubbo-admin.yaml | 3 +-
pkg/common/{errors/error.go => bizerror/common.go} | 2 +-
pkg/console/handler/configurator_rule.go | 2 +-
pkg/console/handler/tag_rule.go | 2 +-
pkg/console/service/application.go | 10 +-
pkg/console/service/instance.go | 2 +-
pkg/console/service/service.go | 2 +-
pkg/core/bootstrap/bootstrap.go | 6 -
.../memory/memory.go => core/bootstrap/init.go} | 14 +-
pkg/core/manager/manager.go | 8 +-
pkg/core/manager/manager_helper.go | 14 +-
pkg/core/store/factory.go | 10 +-
pkg/core/store/index/common.go | 4 +-
pkg/core/store/index/instance.go | 6 +-
pkg/core/store/index/service_consumer_metadata.go | 4 +-
pkg/core/store/index/service_provider_metadata.go | 4 +-
pkg/core/store/store.go | 8 +-
.../index/common.go => store/memory/factory.go} | 35 +-
pkg/store/memory/store.go | 203 ++++++
pkg/store/memory/store_test.go | 777 +++++++++++++++++++++
20 files changed, 1046 insertions(+), 70 deletions(-)
diff --git a/app/dubbo-admin/dubbo-admin.yaml b/app/dubbo-admin/dubbo-admin.yaml
index 8f105ffa..6147444d 100644
--- a/app/dubbo-admin/dubbo-admin.yaml
+++ b/app/dubbo-admin/dubbo-admin.yaml
@@ -37,8 +37,7 @@ console:
password: dubbo@2025
expirationTime: 3600
store:
- type: mysql
- address: xx
+ type: memory
discovery:
- type: nacos
id: nacos-44.33
diff --git a/pkg/common/errors/error.go b/pkg/common/bizerror/common.go
similarity index 98%
rename from pkg/common/errors/error.go
rename to pkg/common/bizerror/common.go
index 19638601..1f993c4b 100644
--- a/pkg/common/errors/error.go
+++ b/pkg/common/bizerror/common.go
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package errors
+package bizerror
import (
"errors"
diff --git a/pkg/console/handler/configurator_rule.go
b/pkg/console/handler/configurator_rule.go
index 37ed7f80..19280c5e 100644
--- a/pkg/console/handler/configurator_rule.go
+++ b/pkg/console/handler/configurator_rule.go
@@ -48,7 +48,7 @@ func ConfiguratorSearch(ctx consolectx.Context)
gin.HandlerFunc {
pageData, err =
manager.PageListByIndexes[*meshresource.DynamicConfigResource](
ctx.ResourceManager(),
meshresource.DynamicConfigKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: req.Mesh,
},
req.PageReq,
diff --git a/pkg/console/handler/tag_rule.go b/pkg/console/handler/tag_rule.go
index 6bc1ab89..7f5756fc 100644
--- a/pkg/console/handler/tag_rule.go
+++ b/pkg/console/handler/tag_rule.go
@@ -49,7 +49,7 @@ func TagRuleSearch(ctx consolectx.Context) gin.HandlerFunc {
pageData, err =
manager.PageListByIndexes[*meshresource.TagRouteResource](
ctx.ResourceManager(),
meshresource.TagRouteKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: req.Mesh,
},
req.PageReq)
diff --git a/pkg/console/service/application.go
b/pkg/console/service/application.go
index ff7f3500..d78f512a 100644
--- a/pkg/console/service/application.go
+++ b/pkg/console/service/application.go
@@ -35,7 +35,7 @@ func GetApplicationDetail(ctx consolectx.Context, req
*model.ApplicationDetailRe
instanceResources, err :=
manager.ListByIndexes[*meshresource.InstanceResource](
ctx.ResourceManager(),
meshresource.InstanceKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: req.Mesh,
index.ByInstanceAppNameIndex: req.AppName,
},
@@ -61,7 +61,7 @@ func GetAppInstanceInfo(ctx consolectx.Context, req
*model.ApplicationTabInstanc
pageData, err :=
manager.PageListByIndexes[*meshresource.InstanceResource](
ctx.ResourceManager(),
meshresource.InstanceKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: req.Mesh,
index.ByInstanceAppNameIndex: req.AppName,
},
@@ -117,7 +117,7 @@ func getAppProvideServiceInfo(ctx consolectx.Context, req
*model.ApplicationServ
pageData, err :=
manager.PageListByIndexes[*meshresource.ServiceProviderMetadataResource](
ctx.ResourceManager(),
meshresource.ServiceProviderMetadataKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: req.Mesh,
index.ByServiceProviderAppName: req.AppName,
},
@@ -160,7 +160,7 @@ func getAppConsumeServiceInfo(ctx consolectx.Context, req
*model.ApplicationServ
pageData, err :=
manager.PageListByIndexes[*meshresource.ServiceConsumerMetadataResource](
ctx.ResourceManager(),
meshresource.ServiceConsumerMetadataKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: req.Mesh,
index.ByServiceConsumerAppName: req.AppName,
},
@@ -239,7 +239,7 @@ func searchApplications(
pageData, err =
manager.PageListByIndexes[*meshresource.ApplicationResource](
ctx.ResourceManager(),
meshresource.ApplicationKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: mesh,
},
pageReq,
diff --git a/pkg/console/service/instance.go b/pkg/console/service/instance.go
index 111a8264..2686390d 100644
--- a/pkg/console/service/instance.go
+++ b/pkg/console/service/instance.go
@@ -53,7 +53,7 @@ func SearchInstances(ctx consolectx.Context, req
*model.SearchInstanceReq) (*mod
pageData, err =
manager.PageListByIndexes[*meshresource.InstanceResource](
ctx.ResourceManager(),
meshresource.InstanceKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: req.Mesh,
},
req.PageReq)
diff --git a/pkg/console/service/service.go b/pkg/console/service/service.go
index 73f079e6..4ff11e88 100644
--- a/pkg/console/service/service.go
+++ b/pkg/console/service/service.go
@@ -47,7 +47,7 @@ func SearchServices(ctx consolectx.Context, req
*model.ServiceSearchReq) (*model
pageData, err =
manager.PageListByIndexes[*meshresource.ServiceResource](
ctx.ResourceManager(),
meshresource.ServiceKind,
- map[string]interface{}{
+ map[string]string{
index.ByMeshIndex: req.Mesh,
},
req.PageReq,
diff --git a/pkg/core/bootstrap/bootstrap.go b/pkg/core/bootstrap/bootstrap.go
index 0b8329f2..213c817f 100644
--- a/pkg/core/bootstrap/bootstrap.go
+++ b/pkg/core/bootstrap/bootstrap.go
@@ -23,14 +23,8 @@ import (
"github.com/pkg/errors"
"github.com/apache/dubbo-admin/pkg/config/app"
- _ "github.com/apache/dubbo-admin/pkg/console"
- _ "github.com/apache/dubbo-admin/pkg/core/discovery"
- _ "github.com/apache/dubbo-admin/pkg/core/engine"
- _ "github.com/apache/dubbo-admin/pkg/core/events"
"github.com/apache/dubbo-admin/pkg/core/logger"
- _ "github.com/apache/dubbo-admin/pkg/core/manager"
"github.com/apache/dubbo-admin/pkg/core/runtime"
- _ "github.com/apache/dubbo-admin/pkg/core/store"
"github.com/apache/dubbo-admin/pkg/diagnostics"
)
diff --git a/pkg/store/memory/memory.go b/pkg/core/bootstrap/init.go
similarity index 67%
rename from pkg/store/memory/memory.go
rename to pkg/core/bootstrap/init.go
index 80a2c7cd..2fecda05 100644
--- a/pkg/store/memory/memory.go
+++ b/pkg/core/bootstrap/init.go
@@ -15,8 +15,14 @@
* limitations under the License.
*/
-package memory
+package bootstrap
-import _ "k8s.io/client-go/tools/cache"
-
-// TODO implement memory resource store, refer to client-go cache.Store
+import (
+ _ "github.com/apache/dubbo-admin/pkg/console"
+ _ "github.com/apache/dubbo-admin/pkg/core/discovery"
+ _ "github.com/apache/dubbo-admin/pkg/core/engine"
+ _ "github.com/apache/dubbo-admin/pkg/core/events"
+ _ "github.com/apache/dubbo-admin/pkg/core/manager"
+ _ "github.com/apache/dubbo-admin/pkg/core/store"
+ _ "github.com/apache/dubbo-admin/pkg/store/memory"
+)
diff --git a/pkg/core/manager/manager.go b/pkg/core/manager/manager.go
index 3cf8685e..2ed0a11f 100644
--- a/pkg/core/manager/manager.go
+++ b/pkg/core/manager/manager.go
@@ -28,9 +28,9 @@ type ReadOnlyResourceManager interface {
// GetByKey returns the resource with the given resource key
GetByKey(rk model.ResourceKind, key string) (r model.Resource, exist
bool, err error)
// ListByIndexes returns the resources with the given indexes, indexes
is a map of index name and index value
- ListByIndexes(rk model.ResourceKind, indexes map[string]interface{})
([]model.Resource, error)
+ ListByIndexes(rk model.ResourceKind, indexes map[string]string)
([]model.Resource, error)
// PageListByIndexes page list the resources with the given indexes,
indexes is a map of index name and index value
- PageListByIndexes(rk model.ResourceKind, indexes
map[string]interface{}, pr model.PageReq) (*model.PageData[model.Resource],
error)
+ PageListByIndexes(rk model.ResourceKind, indexes map[string]string, pr
model.PageReq) (*model.PageData[model.Resource], error)
// PageSearchResourceByConditions page fuzzy search resource by
conditions, conditions cannot be empty
// TODO support multiple conditions
PageSearchResourceByConditions(rk model.ResourceKind, conditions
[]string, pr model.PageReq) (*model.PageData[model.Resource], error)
@@ -74,7 +74,7 @@ func (rm *resourcesManager) GetByKey(rk model.ResourceKind,
key string) (r model
return item.(model.Resource), exist, err
}
-func (rm *resourcesManager) ListByIndexes(rk model.ResourceKind, indexes
map[string]interface{}) ([]model.Resource, error) {
+func (rm *resourcesManager) ListByIndexes(rk model.ResourceKind, indexes
map[string]string) ([]model.Resource, error) {
rs, err := rm.StoreRouter.ResourceKindRoute(rk)
if err != nil {
return nil, err
@@ -88,7 +88,7 @@ func (rm *resourcesManager) ListByIndexes(rk
model.ResourceKind, indexes map[str
func (rm *resourcesManager) PageListByIndexes(
rk model.ResourceKind,
- indexes map[string]interface{},
+ indexes map[string]string,
pr model.PageReq) (*model.PageData[model.Resource], error) {
rs, err := rm.StoreRouter.ResourceKindRoute(rk)
diff --git a/pkg/core/manager/manager_helper.go
b/pkg/core/manager/manager_helper.go
index 7429477f..ff32ce2a 100644
--- a/pkg/core/manager/manager_helper.go
+++ b/pkg/core/manager/manager_helper.go
@@ -20,7 +20,7 @@ package manager
import (
"reflect"
- "github.com/apache/dubbo-admin/pkg/common/errors"
+ "github.com/apache/dubbo-admin/pkg/common/bizerror"
"github.com/apache/dubbo-admin/pkg/core/resource/model"
)
@@ -35,13 +35,13 @@ func GetByKey[T model.Resource](rm ReadOnlyResourceManager,
rk model.ResourceKin
typedResource, ok := resource.(T)
if !ok {
var zero T
- return zero, false, errors.NewAssertionError(rk,
reflect.TypeOf(typedResource).Name())
+ return zero, false, bizerror.NewAssertionError(rk,
reflect.TypeOf(typedResource).Name())
}
return typedResource, true, nil
}
// ListByIndexes is a helper function of ResourceManager.ListByIndexes
-func ListByIndexes[T model.Resource](rm ReadOnlyResourceManager, rk
model.ResourceKind, indexes map[string]interface{}) ([]T, error) {
+func ListByIndexes[T model.Resource](rm ReadOnlyResourceManager, rk
model.ResourceKind, indexes map[string]string) ([]T, error) {
resources, err := rm.ListByIndexes(rk, indexes)
if err != nil {
return nil, err
@@ -51,7 +51,7 @@ func ListByIndexes[T model.Resource](rm
ReadOnlyResourceManager, rk model.Resour
for i, resource := range resources {
typedResource, ok := resource.(T)
if !ok {
- return nil, errors.NewAssertionError(rk,
reflect.TypeOf(typedResource).Name())
+ return nil, bizerror.NewAssertionError(rk,
reflect.TypeOf(typedResource).Name())
}
typedResources[i] = typedResource
}
@@ -63,7 +63,7 @@ func ListByIndexes[T model.Resource](rm
ReadOnlyResourceManager, rk model.Resour
func PageListByIndexes[T model.Resource](
rm ReadOnlyResourceManager,
rk model.ResourceKind,
- indexes map[string]interface{},
+ indexes map[string]string,
pr model.PageReq) (*model.PageData[T], error) {
pageData, err := rm.PageListByIndexes(rk, indexes, pr)
@@ -75,7 +75,7 @@ func PageListByIndexes[T model.Resource](
for i, resource := range pageData.Data {
typedResource, ok := resource.(T)
if !ok {
- return nil, errors.NewAssertionError(rk,
reflect.TypeOf(typedResource).Name())
+ return nil, bizerror.NewAssertionError(rk,
reflect.TypeOf(typedResource).Name())
}
typedResources[i] = typedResource
}
@@ -105,7 +105,7 @@ func PageSearchResourceByConditions[T model.Resource](
for i, resource := range pageData.Data {
typedResource, ok := resource.(T)
if !ok {
- return nil, errors.NewAssertionError(rk,
reflect.TypeOf(typedResource).Name())
+ return nil, bizerror.NewAssertionError(rk,
reflect.TypeOf(typedResource).Name())
}
typedResources[i] = typedResource
}
diff --git a/pkg/core/store/factory.go b/pkg/core/store/factory.go
index 09202b23..e0e9b79e 100644
--- a/pkg/core/store/factory.go
+++ b/pkg/core/store/factory.go
@@ -20,7 +20,7 @@ package store
import (
"fmt"
- "github.com/apache/dubbo-admin/pkg/config/store"
+ storecfg "github.com/apache/dubbo-admin/pkg/config/store"
"github.com/apache/dubbo-admin/pkg/core/resource/model"
)
@@ -37,13 +37,13 @@ func FactoryRegistry() Registry {
// Factory is the interface for create a specific type of ManagedResourceStore
type Factory interface {
// Support returns true if the factory supports the given type in config
- Support(store.Type) bool
+ Support(storecfg.Type) bool
// New returns a new ManagedResourceStore for the model.ResourceKind
using the given config
- New(model.ResourceKind, *store.Config) (ManagedResourceStore, error)
+ New(model.ResourceKind, *storecfg.Config) (ManagedResourceStore, error)
}
type Registry interface {
- GetStoreFactory(store.Type) (Factory, error)
+ GetStoreFactory(storecfg.Type) (Factory, error)
}
type RegistryMutator interface {
@@ -66,7 +66,7 @@ func newStoreFactoryRegistry() MutableRegistry {
factories: make([]Factory, 0),
}
}
-func (s *storeFactoryRegistry) GetStoreFactory(t store.Type) (Factory, error) {
+func (s *storeFactoryRegistry) GetStoreFactory(t storecfg.Type) (Factory,
error) {
for _, factory := range s.factories {
if factory.Support(t) {
return factory, nil
diff --git a/pkg/core/store/index/common.go b/pkg/core/store/index/common.go
index 240bb1e9..f2b69f96 100644
--- a/pkg/core/store/index/common.go
+++ b/pkg/core/store/index/common.go
@@ -23,7 +23,7 @@ import (
"github.com/duke-git/lancet/v2/slice"
"k8s.io/client-go/tools/cache"
- "github.com/apache/dubbo-admin/pkg/common/errors"
+ "github.com/apache/dubbo-admin/pkg/common/bizerror"
coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
)
@@ -41,7 +41,7 @@ func init() {
func ByMesh(obj interface{}) ([]string, error) {
r, ok := obj.(coremodel.Resource)
if !ok {
- return nil, errors.NewAssertionError("Resource",
reflect.TypeOf(obj).Name())
+ return nil, bizerror.NewAssertionError("Resource",
reflect.TypeOf(obj).Name())
}
return []string{r.MeshName()}, nil
}
diff --git a/pkg/core/store/index/instance.go b/pkg/core/store/index/instance.go
index e2d4c86f..f0bbd3fd 100644
--- a/pkg/core/store/index/instance.go
+++ b/pkg/core/store/index/instance.go
@@ -22,7 +22,7 @@ import (
"k8s.io/client-go/tools/cache"
- "github.com/apache/dubbo-admin/pkg/common/errors"
+ "github.com/apache/dubbo-admin/pkg/common/bizerror"
meshresource
"github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
)
@@ -41,7 +41,7 @@ func init() {
func byInstanceAppName(obj interface{}) ([]string, error) {
instance, ok := obj.(*meshresource.InstanceResource)
if !ok {
- return nil, errors.NewAssertionError(meshresource.InstanceKind,
reflect.TypeOf(obj).Name())
+ return nil,
bizerror.NewAssertionError(meshresource.InstanceKind,
reflect.TypeOf(obj).Name())
}
if instance.Spec == nil {
return []string{}, nil
@@ -52,7 +52,7 @@ func byInstanceAppName(obj interface{}) ([]string, error) {
func byIp(obj interface{}) ([]string, error) {
instance, ok := obj.(*meshresource.InstanceResource)
if !ok {
- return nil, errors.NewAssertionError(meshresource.InstanceKind,
reflect.TypeOf(obj).Name())
+ return nil,
bizerror.NewAssertionError(meshresource.InstanceKind,
reflect.TypeOf(obj).Name())
}
if instance.Spec == nil {
return []string{}, nil
diff --git a/pkg/core/store/index/service_consumer_metadata.go
b/pkg/core/store/index/service_consumer_metadata.go
index a5ce6eed..6b5c7fa3 100644
--- a/pkg/core/store/index/service_consumer_metadata.go
+++ b/pkg/core/store/index/service_consumer_metadata.go
@@ -22,7 +22,7 @@ import (
"k8s.io/client-go/tools/cache"
- "github.com/apache/dubbo-admin/pkg/common/errors"
+ "github.com/apache/dubbo-admin/pkg/common/bizerror"
meshresource
"github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
)
@@ -39,7 +39,7 @@ func init() {
func byServiceConsumerAppName(obj interface{}) ([]string, error) {
metadata, ok := obj.(*meshresource.ServiceConsumerMetadataResource)
if !ok {
- return nil,
errors.NewAssertionError(meshresource.ServiceConsumerMetadataKind,
reflect.TypeOf(obj).Name())
+ return nil,
bizerror.NewAssertionError(meshresource.ServiceConsumerMetadataKind,
reflect.TypeOf(obj).Name())
}
if metadata == nil {
return []string{}, nil
diff --git a/pkg/core/store/index/service_provider_metadata.go
b/pkg/core/store/index/service_provider_metadata.go
index 5975150f..53f9b892 100644
--- a/pkg/core/store/index/service_provider_metadata.go
+++ b/pkg/core/store/index/service_provider_metadata.go
@@ -22,7 +22,7 @@ import (
"k8s.io/client-go/tools/cache"
- "github.com/apache/dubbo-admin/pkg/common/errors"
+ "github.com/apache/dubbo-admin/pkg/common/bizerror"
meshresource
"github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
)
@@ -39,7 +39,7 @@ func init() {
func byServiceProviderAppName(obj interface{}) ([]string, error) {
metadata, ok := obj.(*meshresource.ServiceProviderMetadataResource)
if !ok {
- return nil,
errors.NewAssertionError(meshresource.ServiceProviderMetadataKind,
reflect.TypeOf(obj))
+ return nil,
bizerror.NewAssertionError(meshresource.ServiceProviderMetadataKind,
reflect.TypeOf(obj).Name())
}
if metadata.Spec == nil {
return []string{}, nil
diff --git a/pkg/core/store/store.go b/pkg/core/store/store.go
index 04fc8cf9..15d2117e 100644
--- a/pkg/core/store/store.go
+++ b/pkg/core/store/store.go
@@ -33,9 +33,13 @@ import (
// ResourceStore expanded the interface of cache.Indexer and cache.Store
type ResourceStore interface {
Indexer
- ListByIndexes(indexes map[string]interface{}) ([]model.Resource, error)
+ // GetByKeys get resources by keys, return list of resource.
+ // if a resource of specified key doesn't exist in the store, resource
list will not include it
+ GetByKeys(keys []string) ([]model.Resource, error)
+ // ListByIndexes list resources by indexes, indexes is map of index
name and index value
+ ListByIndexes(indexes map[string]string) ([]model.Resource, error)
// PageListByIndexes list resources by indexes pageable, indexes is map
of index name and index value
- PageListByIndexes(indexes map[string]interface{}, pq model.PageReq)
(*model.PageData[model.Resource], error)
+ PageListByIndexes(indexes map[string]string, pq model.PageReq)
(*model.PageData[model.Resource], error)
}
// ManagedResourceStore includes both functional interfaces and lifecycle
interfaces
diff --git a/pkg/core/store/index/common.go b/pkg/store/memory/factory.go
similarity index 60%
copy from pkg/core/store/index/common.go
copy to pkg/store/memory/factory.go
index 240bb1e9..564b32d1 100644
--- a/pkg/core/store/index/common.go
+++ b/pkg/store/memory/factory.go
@@ -15,33 +15,26 @@
* limitations under the License.
*/
-package index
+package memory
import (
- "reflect"
-
- "github.com/duke-git/lancet/v2/slice"
- "k8s.io/client-go/tools/cache"
-
- "github.com/apache/dubbo-admin/pkg/common/errors"
+ storecfg "github.com/apache/dubbo-admin/pkg/config/store"
coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
+ "github.com/apache/dubbo-admin/pkg/core/store"
)
-const ByMeshIndex = "idx_mesh"
-
func init() {
- rks := coremodel.ResourceSchemaRegistry().AllResourceKinds()
- slice.ForEach(rks, func(_ int, rk coremodel.ResourceKind) {
- RegisterIndexers(rk, map[string]cache.IndexFunc{
- ByMeshIndex: ByMesh,
- })
- })
+ store.RegisterFactory(&storeFactory{})
+}
+
+type storeFactory struct{}
+
+var _ store.Factory = &storeFactory{}
+
+func (sf *storeFactory) Support(s storecfg.Type) bool {
+ return s == storecfg.Memory
}
-func ByMesh(obj interface{}) ([]string, error) {
- r, ok := obj.(coremodel.Resource)
- if !ok {
- return nil, errors.NewAssertionError("Resource",
reflect.TypeOf(obj).Name())
- }
- return []string{r.MeshName()}, nil
+func (sf *storeFactory) New(_ coremodel.ResourceKind, _ *storecfg.Config)
(store.ManagedResourceStore, error) {
+ return NewMemoryResourceStore(), nil
}
diff --git a/pkg/store/memory/store.go b/pkg/store/memory/store.go
new file mode 100644
index 00000000..e522cb06
--- /dev/null
+++ b/pkg/store/memory/store.go
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package memory
+
+import (
+ "reflect"
+ "sort"
+
+ set "github.com/duke-git/lancet/v2/datastructure/set"
+ "k8s.io/client-go/tools/cache"
+
+ "github.com/apache/dubbo-admin/pkg/common/bizerror"
+ "github.com/apache/dubbo-admin/pkg/common/util/slices"
+ coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
+ "github.com/apache/dubbo-admin/pkg/core/runtime"
+ "github.com/apache/dubbo-admin/pkg/core/store"
+)
+
+type resourceStore struct {
+ storeProxy cache.Indexer
+}
+
+var _ store.ManagedResourceStore = &resourceStore{}
+
+func NewMemoryResourceStore() store.ManagedResourceStore {
+ return &resourceStore{}
+}
+
+func (rs *resourceStore) Init(_ runtime.BuilderContext) error {
+ rs.storeProxy = cache.NewIndexer(
+ func(obj interface{}) (string, error) {
+ r, ok := obj.(coremodel.Resource)
+ if !ok {
+ return "",
bizerror.NewAssertionError("Resource", reflect.TypeOf(obj).Name())
+ }
+ return r.ResourceKey(), nil
+ },
+ cache.Indexers{},
+ )
+ return nil
+}
+
+func (rs *resourceStore) Start(_ runtime.Runtime, _ <-chan struct{}) error {
+ return nil
+}
+
+func (rs *resourceStore) Add(obj interface{}) error {
+ return rs.storeProxy.Add(obj)
+}
+
+func (rs *resourceStore) Update(obj interface{}) error {
+ return rs.storeProxy.Update(obj)
+}
+
+func (rs *resourceStore) Delete(obj interface{}) error {
+ return rs.storeProxy.Delete(obj)
+}
+
+func (rs *resourceStore) List() []interface{} {
+ return rs.storeProxy.List()
+}
+
+func (rs *resourceStore) ListKeys() []string {
+ return rs.storeProxy.ListKeys()
+}
+
+func (rs *resourceStore) Get(obj interface{}) (item interface{}, exists bool,
err error) {
+ return rs.storeProxy.Get(obj)
+}
+
+func (rs *resourceStore) GetByKey(key string) (item interface{}, exists bool,
err error) {
+ return rs.storeProxy.GetByKey(key)
+}
+
+func (rs *resourceStore) Replace(i []interface{}, s string) error {
+ return rs.storeProxy.Replace(i, s)
+}
+
+func (rs *resourceStore) Resync() error {
+ return rs.storeProxy.Resync()
+}
+
+func (rs *resourceStore) Index(indexName string, obj interface{})
([]interface{}, error) {
+ return rs.storeProxy.Index(indexName, obj)
+}
+
+func (rs *resourceStore) IndexKeys(indexName, indexedValue string) ([]string,
error) {
+ return rs.storeProxy.IndexKeys(indexName, indexedValue)
+}
+
+func (rs *resourceStore) ListIndexFuncValues(indexName string) []string {
+ return rs.storeProxy.ListIndexFuncValues(indexName)
+}
+
+func (rs *resourceStore) ByIndex(indexName, indexedValue string)
([]interface{}, error) {
+ return rs.storeProxy.ByIndex(indexName, indexedValue)
+}
+
+func (rs *resourceStore) GetIndexers() cache.Indexers {
+ return rs.storeProxy.GetIndexers()
+}
+
+func (rs *resourceStore) AddIndexers(newIndexers cache.Indexers) error {
+ return rs.storeProxy.AddIndexers(newIndexers)
+}
+
+func (rs *resourceStore) GetByKeys(keys []string) ([]coremodel.Resource,
error) {
+ resources := make([]coremodel.Resource, 0)
+ for _, key := range keys {
+ r, exists, err := rs.storeProxy.GetByKey(key)
+ if err != nil {
+ return nil, err
+ }
+ if !exists {
+ continue
+ }
+ res, ok := r.(coremodel.Resource)
+ if !ok {
+ return nil, bizerror.NewAssertionError("Resource",
reflect.TypeOf(r).Name())
+ }
+ resources = append(resources, res)
+ }
+ return resources, nil
+}
+
+func (rs *resourceStore) ListByIndexes(indexes map[string]string)
([]coremodel.Resource, error) {
+ keys, err := rs.getKeysByIndexes(indexes)
+ if err != nil {
+ return nil, err
+ }
+ resources, err := rs.GetByKeys(keys)
+ if err != nil {
+ return nil, err
+ }
+ resources = slices.SortBy(resources, func(r coremodel.Resource) string {
+ return r.ResourceKey()
+ })
+ return resources, nil
+}
+
+func (rs *resourceStore) PageListByIndexes(indexes map[string]string, pq
coremodel.PageReq) (*coremodel.PageData[coremodel.Resource], error) {
+ keys, err := rs.getKeysByIndexes(indexes)
+ if err != nil {
+ return nil, err
+ }
+ sort.Strings(keys)
+ total := len(keys)
+ resources := make([]coremodel.Resource, 0, pq.PageSize)
+ for i := pq.PageOffset; len(resources) < pq.PageSize && i < total; i++ {
+ r, exists, err := rs.storeProxy.GetByKey(keys[i])
+ if err != nil {
+ return nil, err
+ }
+ if !exists {
+ total -= 1
+ continue
+ }
+ res, ok := r.(coremodel.Resource)
+ if !ok {
+ return nil, bizerror.NewAssertionError("Resource",
reflect.TypeOf(r).Name())
+ }
+ resources = append(resources, res)
+ }
+ pageData := coremodel.NewPageData(total, pq.PageOffset, pq.PageSize,
resources)
+ return pageData, nil
+}
+
+func (rs *resourceStore) getKeysByIndexes(indexes map[string]string)
([]string, error) {
+ if len(indexes) == 0 {
+ return []string{}, nil
+ }
+ keySet := set.New[string]()
+ first := true
+ for indexName, indexValue := range indexes {
+ keys, err := rs.storeProxy.IndexKeys(indexName, indexValue)
+ if err != nil {
+ return nil, err
+ }
+ if first {
+ keySet = set.FromSlice(keys)
+ first = false
+ } else {
+ nextSet := set.FromSlice(keys)
+ keySet = keySet.Intersection(nextSet)
+ }
+ }
+ return keySet.ToSlice(), nil
+}
diff --git a/pkg/store/memory/store_test.go b/pkg/store/memory/store_test.go
new file mode 100644
index 00000000..f78c88c0
--- /dev/null
+++ b/pkg/store/memory/store_test.go
@@ -0,0 +1,777 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package memory
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/client-go/tools/cache"
+
+ "github.com/apache/dubbo-admin/pkg/core/resource/model"
+)
+
+// mockResource is a mock implementation of model.Resource for testing
+type mockResource struct {
+ kind model.ResourceKind
+ key string
+ mesh string
+ meta metav1.ObjectMeta
+ spec model.ResourceSpec
+ objectRef runtime.Object
+}
+
+func (mr *mockResource) GetObjectKind() schema.ObjectKind {
+ return schema.EmptyObjectKind
+}
+
+func (mr *mockResource) DeepCopyObject() runtime.Object {
+ return mr.objectRef
+}
+
+func (mr *mockResource) ResourceKind() model.ResourceKind {
+ return mr.kind
+}
+
+func (mr *mockResource) ResourceKey() string {
+ return mr.key
+}
+
+func (mr *mockResource) MeshName() string {
+ return mr.mesh
+}
+
+func (mr *mockResource) ResourceMeta() metav1.ObjectMeta {
+ return mr.meta
+}
+
+func (mr *mockResource) ResourceSpec() model.ResourceSpec {
+ return mr.spec
+}
+
+func TestNewMemoryResourceStore(t *testing.T) {
+ store := NewMemoryResourceStore()
+ assert.NotNil(t, store)
+}
+
+func TestResourceStore_Init(t *testing.T) {
+ store := &resourceStore{}
+ err := store.Init(nil)
+ assert.NoError(t, err)
+ assert.NotNil(t, store.storeProxy)
+}
+
+func TestResourceStore_AddAndGet(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create a mock resource
+ mockRes := &mockResource{
+ kind: "TestResource",
+ key: "test-key",
+ mesh: "default",
+ meta: metav1.ObjectMeta{
+ Name: "test-resource",
+ Namespace: "default",
+ },
+ }
+
+ // Add the resource
+ err = store.Add(mockRes)
+ assert.NoError(t, err)
+
+ // Get the resource
+ item, exists, err := store.Get(mockRes)
+ assert.NoError(t, err)
+ assert.True(t, exists)
+ assert.Equal(t, mockRes, item)
+
+ // Get by key
+ item, exists, err = store.GetByKey("test-key")
+ assert.NoError(t, err)
+ assert.True(t, exists)
+ assert.Equal(t, mockRes, item)
+}
+
+func TestResourceStore_Update(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create a mock resource
+ mockRes := &mockResource{
+ kind: "TestResource",
+ key: "test-key",
+ mesh: "default",
+ meta: metav1.ObjectMeta{
+ Name: "test-resource",
+ Namespace: "default",
+ },
+ }
+
+ // Add the resource
+ err = store.Add(mockRes)
+ assert.NoError(t, err)
+
+ // Update the resource
+ updatedRes := &mockResource{
+ kind: "TestResource",
+ key: "test-key",
+ mesh: "default",
+ meta: metav1.ObjectMeta{
+ Name: "updated-resource",
+ Namespace: "default",
+ },
+ }
+
+ err = store.Update(updatedRes)
+ assert.NoError(t, err)
+
+ // Get the updated resource
+ item, exists, err := store.Get(updatedRes)
+ assert.NoError(t, err)
+ assert.True(t, exists)
+ assert.Equal(t, updatedRes, item)
+ assert.Equal(t, "updated-resource", item.(*mockResource).meta.Name)
+}
+
+func TestResourceStore_Delete(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create a mock resource
+ mockRes := &mockResource{
+ kind: "TestResource",
+ key: "test-key",
+ mesh: "default",
+ meta: metav1.ObjectMeta{
+ Name: "test-resource",
+ Namespace: "default",
+ },
+ }
+
+ // Add the resource
+ err = store.Add(mockRes)
+ assert.NoError(t, err)
+
+ // Delete the resource
+ err = store.Delete(mockRes)
+ assert.NoError(t, err)
+
+ // Try to get the deleted resource
+ _, exists, err := store.Get(mockRes)
+ assert.NoError(t, err)
+ assert.False(t, exists)
+}
+
+func TestResourceStore_List(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestResource",
+ key: "test-key-1",
+ mesh: "default",
+ meta: metav1.ObjectMeta{Name: "test-resource-1"},
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestResource",
+ key: "test-key-2",
+ mesh: "default",
+ meta: metav1.ObjectMeta{Name: "test-resource-2"},
+ }
+
+ // Add resources
+ err = store.Add(mockRes1)
+ assert.NoError(t, err)
+ err = store.Add(mockRes2)
+ assert.NoError(t, err)
+
+ // List resources
+ list := store.List()
+ assert.Len(t, list, 2)
+ assert.Contains(t, list, mockRes1)
+ assert.Contains(t, list, mockRes2)
+}
+
+func TestResourceStore_ListKeys(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestResource",
+ key: "test-key-1",
+ mesh: "default",
+ meta: metav1.ObjectMeta{Name: "test-resource-1"},
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestResource",
+ key: "test-key-2",
+ mesh: "default",
+ meta: metav1.ObjectMeta{Name: "test-resource-2"},
+ }
+
+ // Add resources
+ err = store.Add(mockRes1)
+ assert.NoError(t, err)
+ err = store.Add(mockRes2)
+ assert.NoError(t, err)
+
+ // List keys
+ keys := store.ListKeys()
+ assert.Len(t, keys, 2)
+ assert.Contains(t, keys, "test-key-1")
+ assert.Contains(t, keys, "test-key-2")
+}
+
+func TestResourceStore_Replace(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create initial mock resources
+ mockRes1 := &mockResource{
+ kind: "TestResource",
+ key: "test-key-1",
+ mesh: "default",
+ meta: metav1.ObjectMeta{Name: "test-resource-1"},
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestResource",
+ key: "test-key-2",
+ mesh: "default",
+ meta: metav1.ObjectMeta{Name: "test-resource-2"},
+ }
+
+ // Add resources
+ err = store.Add(mockRes1)
+ assert.NoError(t, err)
+
+ // Replace with new set of resources
+ newResources := []interface{}{mockRes2}
+ err = store.Replace(newResources, "version-1")
+ assert.NoError(t, err)
+
+ // Check that only the new resource exists
+ keys := store.ListKeys()
+ assert.Len(t, keys, 1)
+ assert.Contains(t, keys, "test-key-2")
+ assert.NotContains(t, keys, "test-key-1")
+}
+
+func TestResourceStore_GetByKeys(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestResource",
+ key: "test-key-1",
+ mesh: "default",
+ meta: metav1.ObjectMeta{Name: "test-resource-1"},
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestResource",
+ key: "test-key-2",
+ mesh: "default",
+ meta: metav1.ObjectMeta{Name: "test-resource-2"},
+ }
+
+ // Add only first resource
+ err = store.Add(mockRes1)
+ assert.NoError(t, err)
+
+ err = store.Add(mockRes2)
+ assert.NoError(t, err)
+
+ // Get by multiple keys
+ keys := []string{"test-key-1", "test-key-2", "test-key-3"}
+ resources, err := store.GetByKeys(keys)
+ assert.NoError(t, err)
+ assert.Len(t, resources, 2)
+ assert.Equal(t, mockRes1, resources[0])
+ assert.Equal(t, mockRes2, resources[1])
+}
+
+func TestResourceStore_ListByIndexes(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestResource",
+ key: "mesh1/test-key-1",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{Name: "test-resource-1"},
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestResource",
+ key: "mesh1/test-key-2",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{Name: "test-resource-2"},
+ }
+
+ mockRes3 := &mockResource{
+ kind: "TestResource",
+ key: "mesh2/test-key-3",
+ mesh: "mesh2",
+ meta: metav1.ObjectMeta{Name: "test-resource-3"},
+ }
+
+ // Add indexers
+ indexers := map[string]cache.IndexFunc{
+ "by-mesh": func(obj interface{}) ([]string, error) {
+ resource := obj.(model.Resource)
+ return []string{resource.MeshName()}, nil
+ },
+ }
+ err = store.AddIndexers(indexers)
+ assert.NoError(t, err)
+
+ // Add resources
+ err = store.Add(mockRes1)
+ assert.NoError(t, err)
+ err = store.Add(mockRes2)
+ assert.NoError(t, err)
+ err = store.Add(mockRes3)
+ assert.NoError(t, err)
+
+ // List by indexes
+ indexes := map[string]string{"by-mesh": "mesh1"}
+ resources, err := store.ListByIndexes(indexes)
+ assert.NoError(t, err)
+ assert.Len(t, resources, 2)
+ // Should be sorted by ResourceKey
+ assert.Equal(t, mockRes1, resources[0])
+ assert.Equal(t, mockRes2, resources[1])
+}
+
+func TestResourceStore_PageListByIndexes(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestResource",
+ key: "mesh1/test-key-1",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{Name: "test-resource-1"},
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestResource",
+ key: "mesh1/test-key-2",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{Name: "test-resource-2"},
+ }
+
+ mockRes3 := &mockResource{
+ kind: "TestResource",
+ key: "mesh1/test-key-3",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{Name: "test-resource-3"},
+ }
+
+ // Add indexers
+ indexers := map[string]cache.IndexFunc{
+ "by-mesh": func(obj interface{}) ([]string, error) {
+ resource := obj.(model.Resource)
+ return []string{resource.MeshName()}, nil
+ },
+ }
+ err = store.AddIndexers(indexers)
+ assert.NoError(t, err)
+
+ // Add resources
+ err = store.Add(mockRes1)
+ assert.NoError(t, err)
+ err = store.Add(mockRes2)
+ assert.NoError(t, err)
+ err = store.Add(mockRes3)
+ assert.NoError(t, err)
+
+ // Page list by indexes
+ indexes := map[string]string{"by-mesh": "mesh1"}
+ pageReq := model.PageReq{
+ PageOffset: 0,
+ PageSize: 2,
+ }
+ pageData, err := store.PageListByIndexes(indexes, pageReq)
+ assert.NoError(t, err)
+ // Total 3 resources
+ assert.Equal(t, 3, pageData.Total)
+ // Page offset 0
+ assert.Equal(t, 0, pageData.PageOffset)
+ // Page size 2
+ assert.Equal(t, 2, pageData.PageSize)
+ // 2 items in this page
+ assert.Len(t, pageData.Data, 2)
+ // Sorted by key
+ assert.Equal(t, mockRes1, pageData.Data[0])
+ assert.Equal(t, mockRes2, pageData.Data[1])
+
+ // Second page
+ pageReq.PageOffset = 2
+ pageData, err = store.PageListByIndexes(indexes, pageReq)
+ assert.NoError(t, err)
+ // Total still 3
+ assert.Equal(t, 3, pageData.Total)
+ // Page offset 2
+ assert.Equal(t, 2, pageData.PageOffset)
+ // Page size 2
+ assert.Equal(t, 2, pageData.PageSize)
+ // Only 1 item left
+ assert.Len(t, pageData.Data, 1)
+ // Last item
+ assert.Equal(t, mockRes3, pageData.Data[0])
+}
+
+func TestResourceStore_MultipleIndexes(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestApplication",
+ key: "mesh1/app1",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "app1",
+ Namespace: "default",
+ Labels: map[string]string{
+ "version": "v1",
+ "env": "prod",
+ },
+ },
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestApplication",
+ key: "mesh1/app2",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "app2",
+ Namespace: "default",
+ Labels: map[string]string{
+ "version": "v2",
+ "env": "prod",
+ },
+ },
+ }
+
+ mockRes3 := &mockResource{
+ kind: "TestApplication",
+ key: "mesh2/app3",
+ mesh: "mesh2",
+ meta: metav1.ObjectMeta{
+ Name: "app3",
+ Namespace: "default",
+ Labels: map[string]string{
+ "version": "v1",
+ "env": "dev",
+ },
+ },
+ }
+
+ // Add indexers for multiple fields
+ indexers := map[string]cache.IndexFunc{
+ "by-mesh": func(obj interface{}) ([]string, error) {
+ resource := obj.(model.Resource)
+ return []string{resource.MeshName()}, nil
+ },
+ "by-version": func(obj interface{}) ([]string, error) {
+ resource := obj.(model.Resource)
+ version := resource.ResourceMeta().Labels["version"]
+ return []string{version}, nil
+ },
+ "by-env": func(obj interface{}) ([]string, error) {
+ resource := obj.(model.Resource)
+ env := resource.ResourceMeta().Labels["env"]
+ return []string{env}, nil
+ },
+ }
+ err = store.AddIndexers(indexers)
+ assert.NoError(t, err)
+
+ // Add resources
+ resources := []model.Resource{mockRes1, mockRes2, mockRes3}
+ for _, res := range resources {
+ err = store.Add(res)
+ assert.NoError(t, err)
+ }
+
+ // Test multiple indexes - get all prod env resources in mesh1
+ indexes := map[string]string{
+ "by-mesh": "mesh1",
+ "by-version": "v1",
+ }
+ result, err := store.ListByIndexes(indexes)
+ assert.NoError(t, err)
+ assert.Len(t, result, 1)
+ // Should contain app1
+ keys := make([]string, len(result))
+ for i, res := range result {
+ keys[i] = res.ResourceKey()
+ }
+ assert.Contains(t, keys, "mesh1/app1")
+}
+
+func TestResourceStore_IndexKeys(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestService",
+ key: "mesh1/service1",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "service1",
+ Labels: map[string]string{
+ "group": "frontend",
+ },
+ },
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestService",
+ key: "mesh1/service2",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "service2",
+ Labels: map[string]string{
+ "group": "backend",
+ },
+ },
+ }
+
+ mockRes3 := &mockResource{
+ kind: "TestService",
+ key: "mesh1/service3",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "service3",
+ Labels: map[string]string{
+ "group": "frontend",
+ },
+ },
+ }
+
+ // Add indexer
+ indexers := map[string]cache.IndexFunc{
+ "by-group": func(obj interface{}) ([]string, error) {
+ resource := obj.(model.Resource)
+ group := resource.ResourceMeta().Labels["group"]
+ return []string{group}, nil
+ },
+ }
+ err = store.AddIndexers(indexers)
+ assert.NoError(t, err)
+
+ // Add resources
+ resources := []model.Resource{mockRes1, mockRes2, mockRes3}
+ for _, res := range resources {
+ err = store.Add(res)
+ assert.NoError(t, err)
+ }
+
+ // Test IndexKeys method
+ keys, err := store.IndexKeys("by-group", "frontend")
+ assert.NoError(t, err)
+ assert.Len(t, keys, 2)
+ assert.Contains(t, keys, "mesh1/service1")
+ assert.Contains(t, keys, "mesh1/service3")
+
+ keys, err = store.IndexKeys("by-group", "backend")
+ assert.NoError(t, err)
+ assert.Len(t, keys, 1)
+ assert.Contains(t, keys, "mesh1/service2")
+}
+
+func TestResourceStore_ByIndex(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestInstance",
+ key: "mesh1/instance1",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "instance1",
+ Labels: map[string]string{
+ "type": "web",
+ },
+ },
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestInstance",
+ key: "mesh1/instance2",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "instance2",
+ Labels: map[string]string{
+ "type": "database",
+ },
+ },
+ }
+
+ // Add indexer
+ indexers := map[string]cache.IndexFunc{
+ "by-type": func(obj interface{}) ([]string, error) {
+ resource := obj.(model.Resource)
+ instanceType := resource.ResourceMeta().Labels["type"]
+ return []string{instanceType}, nil
+ },
+ }
+ err = store.AddIndexers(indexers)
+ assert.NoError(t, err)
+
+ // Add resources
+ err = store.Add(mockRes1)
+ assert.NoError(t, err)
+ err = store.Add(mockRes2)
+ assert.NoError(t, err)
+
+ // Test ByIndex method
+ items, err := store.ByIndex("by-type", "web")
+ assert.NoError(t, err)
+ assert.Len(t, items, 1)
+ assert.Equal(t, mockRes1, items[0])
+
+ items, err = store.ByIndex("by-type", "database")
+ assert.NoError(t, err)
+ assert.Len(t, items, 1)
+ assert.Equal(t, mockRes2, items[0])
+}
+
+func TestResourceStore_GetIndexers(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Initially no indexers
+ indexers := store.GetIndexers()
+ assert.Empty(t, indexers)
+
+ // Add indexers
+ newIndexers := map[string]cache.IndexFunc{
+ "by-name": func(obj interface{}) ([]string, error) {
+ return
[]string{obj.(model.Resource).ResourceMeta().Name}, nil
+ },
+ "by-kind": func(obj interface{}) ([]string, error) {
+ return
[]string{string(obj.(model.Resource).ResourceKind())}, nil
+ },
+ }
+ err = store.AddIndexers(newIndexers)
+ assert.NoError(t, err)
+
+ // Check if indexers were added
+ indexers = store.GetIndexers()
+ assert.Len(t, indexers, 2)
+ assert.Contains(t, indexers, "by-name")
+ assert.Contains(t, indexers, "by-kind")
+}
+
+func TestResourceStore_ListIndexFuncValues(t *testing.T) {
+ store := NewMemoryResourceStore()
+ err := store.Init(nil)
+ assert.NoError(t, err)
+
+ // Create mock resources
+ mockRes1 := &mockResource{
+ kind: "TestResource",
+ key: "mesh1/resource1",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "resource1",
+ Labels: map[string]string{
+ "status": "active",
+ },
+ },
+ }
+
+ mockRes2 := &mockResource{
+ kind: "TestResource",
+ key: "mesh1/resource2",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "resource2",
+ Labels: map[string]string{
+ "status": "inactive",
+ },
+ },
+ }
+
+ mockRes3 := &mockResource{
+ kind: "TestResource",
+ key: "mesh1/resource3",
+ mesh: "mesh1",
+ meta: metav1.ObjectMeta{
+ Name: "resource3",
+ Labels: map[string]string{
+ "status": "active",
+ },
+ },
+ }
+
+ // Add indexer
+ indexers := map[string]cache.IndexFunc{
+ "by-status": func(obj interface{}) ([]string, error) {
+ resource := obj.(model.Resource)
+ status := resource.ResourceMeta().Labels["status"]
+ return []string{status}, nil
+ },
+ }
+ err = store.AddIndexers(indexers)
+ assert.NoError(t, err)
+
+ // Add resources
+ resources := []model.Resource{mockRes1, mockRes2, mockRes3}
+ for _, res := range resources {
+ err = store.Add(res)
+ assert.NoError(t, err)
+ }
+
+ // Test ListIndexFuncValues method
+ values := store.ListIndexFuncValues("by-status")
+ assert.Len(t, values, 2)
+ assert.Contains(t, values, "active")
+ assert.Contains(t, values, "inactive")
+}