This is an automated email from the ASF dual-hosted git repository.

liujun pushed a commit to branch refactor-with-go
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git


The following commit(s) were added to refs/heads/refactor-with-go by this push:
     new 8d4b93e8 feat: refactor dynamic config with go (#1032)
8d4b93e8 is described below

commit 8d4b93e87f4f3e922d67e50167057678e1f9b7c2
Author: WangGuan <[email protected]>
AuthorDate: Tue Mar 21 14:29:46 2023 +0800

    feat: refactor dynamic config with go (#1032)
---
 go.mod                                             |   3 +-
 go.sum                                             |   4 +-
 pkg/admin/config/governance_config.go              |  46 ++-
 pkg/admin/config/governance_config_mock.go         | 166 ++++++++++
 pkg/admin/constant/const.go                        |  76 +++--
 pkg/admin/handlers/overrides.go                    | 155 +++++++++
 pkg/admin/model/{entity.go => base.go}             |  11 +-
 pkg/admin/model/dynamic_config.go                  |  76 +++++
 pkg/admin/model/entity.go                          |  35 +-
 pkg/admin/model/match.go                           | 108 +++++++
 pkg/admin/model/override.go                        |  97 ++++++
 pkg/admin/router/router.go                         |  11 +
 .../entity.go => services/override_service.go}     |  16 +-
 pkg/admin/services/override_service_impl.go        | 358 +++++++++++++++++++++
 pkg/admin/services/override_service_impl_test.go   | 332 +++++++++++++++++++
 pkg/admin/util/override_utils.go                   |  59 ++++
 pkg/admin/util/override_utils_test.go              |  74 +++++
 17 files changed, 1571 insertions(+), 56 deletions(-)

diff --git a/go.mod b/go.mod
index f33d417e..92f3d0b2 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,9 @@ go 1.19
 
 require (
        dubbo.apache.org/dubbo-go/v3 v3.0.2
+       github.com/dubbogo/gost v1.11.25
        github.com/gin-gonic/gin v1.8.2
+       github.com/golang/mock v1.6.0
        github.com/golang-jwt/jwt/v4 v4.5.0
        github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
        github.com/mattbaird/jsonpatch v0.0.0-20200820163806-098863c1fc24
@@ -52,7 +54,6 @@ require (
        github.com/creasty/defaults v1.5.2 // indirect
        github.com/davecgh/go-spew v1.1.1 // indirect
        github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5 // 
indirect
-       github.com/dubbogo/gost v1.11.25 // indirect
        github.com/dubbogo/triple v1.1.8 // indirect
        github.com/emicklei/go-restful/v3 v3.9.0 // indirect
        github.com/evanphx/json-patch v4.12.0+incompatible // indirect
diff --git a/go.sum b/go.sum
index f227aef0..955d3a2c 100644
--- a/go.sum
+++ b/go.sum
@@ -291,8 +291,9 @@ github.com/golang/mock v1.4.0/go.mod 
h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
 github.com/golang/mock v1.4.1/go.mod 
h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.3/go.mod 
h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod 
h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
 github.com/golang/mock v1.5.0/go.mod 
h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod 
h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -1089,6 +1090,7 @@ golang.org/x/tools 
v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod 
h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20201014170642-d1624618ad65/go.mod 
h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod 
h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.1/go.mod 
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.2/go.mod 
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod 
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pkg/admin/config/governance_config.go 
b/pkg/admin/config/governance_config.go
index e5a61c6a..b58e7d4d 100644
--- a/pkg/admin/config/governance_config.go
+++ b/pkg/admin/config/governance_config.go
@@ -20,38 +20,66 @@ package config
 import (
        "errors"
 
+       "dubbo.apache.org/dubbo-go/v3/common"
        "dubbo.apache.org/dubbo-go/v3/config_center"
 )
 
-func SetConfig(key string, value string) error {
-       return SetConfigWithGroup(Group, key, value)
+type GovernanceConfig interface {
+       SetConfig(key string, value string) error
+       GetConfig(key string) (string, error)
+       DeleteConfig(key string) error
+       SetConfigWithGroup(group string, key string, value string) error
+       GetConfigWithGroup(group string, key string) (string, error)
+       DeleteConfigWithGroup(group string, key string) error
+       Register(url *common.URL) error
+       UnRegister(url *common.URL) error
 }
 
-func GetConfig(key string) (string, error) {
-       return GetConfigWithGroup(Group, key)
+type GovernanceConfigImpl struct{}
+
+func (g *GovernanceConfigImpl) SetConfig(key string, value string) error {
+       return g.SetConfigWithGroup(Group, key, value)
+}
+
+func (g *GovernanceConfigImpl) GetConfig(key string) (string, error) {
+       return g.GetConfigWithGroup(Group, key)
 }
 
-func DeleteConfig(key string) error {
-       return DeleteConfigWithGroup(Group, key)
+func (g *GovernanceConfigImpl) DeleteConfig(key string) error {
+       return g.DeleteConfigWithGroup(Group, key)
 }
 
-func SetConfigWithGroup(group string, key string, value string) error {
+func (g *GovernanceConfigImpl) SetConfigWithGroup(group string, key string, 
value string) error {
        if key == "" || value == "" {
                return errors.New("key or value is empty")
        }
        return ConfigCenter.PublishConfig(key, group, value)
 }
 
-func GetConfigWithGroup(group string, key string) (string, error) {
+func (g *GovernanceConfigImpl) GetConfigWithGroup(group string, key string) 
(string, error) {
        if key == "" {
                return "", errors.New("key is empty")
        }
        return ConfigCenter.GetProperties(key, config_center.WithGroup(group))
 }
 
-func DeleteConfigWithGroup(group string, key string) error {
+func (g *GovernanceConfigImpl) DeleteConfigWithGroup(group string, key string) 
error {
        if key == "" {
                return errors.New("key is empty")
        }
        return ConfigCenter.RemoveConfig(key, group)
 }
+
+func (g *GovernanceConfigImpl) Register(url *common.URL) error {
+       if url.String() == "" {
+               return errors.New("url is empty")
+       }
+       return RegistryCenter.Register(url)
+}
+
+func (g *GovernanceConfigImpl) UnRegister(url *common.URL) error {
+       if url.String() == "" {
+               return errors.New("url is empty")
+       }
+       return RegistryCenter.UnRegister(url)
+}
diff --git a/pkg/admin/config/governance_config_mock.go 
b/pkg/admin/config/governance_config_mock.go
new file mode 100644
index 00000000..0904812c
--- /dev/null
+++ b/pkg/admin/config/governance_config_mock.go
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/apache/dubbo-admin/pkg/admin/config (interfaces: 
GovernanceConfig)
+
+// Package config is a generated GoMock package.
+package config
+
+import (
+       reflect "reflect"
+
+       common "dubbo.apache.org/dubbo-go/v3/common"
+       gomock "github.com/golang/mock/gomock"
+)
+
+// MockGovernanceConfig is a mock of GovernanceConfig interface.
+type MockGovernanceConfig struct {
+       ctrl     *gomock.Controller
+       recorder *MockGovernanceConfigMockRecorder
+}
+
+// MockGovernanceConfigMockRecorder is the mock recorder for 
MockGovernanceConfig.
+type MockGovernanceConfigMockRecorder struct {
+       mock *MockGovernanceConfig
+}
+
+// NewMockGovernanceConfig creates a new mock instance.
+func NewMockGovernanceConfig(ctrl *gomock.Controller) *MockGovernanceConfig {
+       mock := &MockGovernanceConfig{ctrl: ctrl}
+       mock.recorder = &MockGovernanceConfigMockRecorder{mock}
+       return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockGovernanceConfig) EXPECT() *MockGovernanceConfigMockRecorder {
+       return m.recorder
+}
+
+// DeleteConfig mocks base method.
+func (m *MockGovernanceConfig) DeleteConfig(arg0 string) error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "DeleteConfig", arg0)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// DeleteConfig indicates an expected call of DeleteConfig.
+func (mr *MockGovernanceConfigMockRecorder) DeleteConfig(arg0 interface{}) 
*gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteConfig", 
reflect.TypeOf((*MockGovernanceConfig)(nil).DeleteConfig), arg0)
+}
+
+// DeleteConfigWithGroup mocks base method.
+func (m *MockGovernanceConfig) DeleteConfigWithGroup(arg0, arg1 string) error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "DeleteConfigWithGroup", arg0, arg1)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// DeleteConfigWithGroup indicates an expected call of DeleteConfigWithGroup.
+func (mr *MockGovernanceConfigMockRecorder) DeleteConfigWithGroup(arg0, arg1 
interface{}) *gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, 
"DeleteConfigWithGroup", 
reflect.TypeOf((*MockGovernanceConfig)(nil).DeleteConfigWithGroup), arg0, arg1)
+}
+
+// GetConfig mocks base method.
+func (m *MockGovernanceConfig) GetConfig(arg0 string) (string, error) {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "GetConfig", arg0)
+       ret0, _ := ret[0].(string)
+       ret1, _ := ret[1].(error)
+       return ret0, ret1
+}
+
+// GetConfig indicates an expected call of GetConfig.
+func (mr *MockGovernanceConfigMockRecorder) GetConfig(arg0 interface{}) 
*gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfig", 
reflect.TypeOf((*MockGovernanceConfig)(nil).GetConfig), arg0)
+}
+
+// GetConfigWithGroup mocks base method.
+func (m *MockGovernanceConfig) GetConfigWithGroup(arg0, arg1 string) (string, 
error) {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "GetConfigWithGroup", arg0, arg1)
+       ret0, _ := ret[0].(string)
+       ret1, _ := ret[1].(error)
+       return ret0, ret1
+}
+
+// GetConfigWithGroup indicates an expected call of GetConfigWithGroup.
+func (mr *MockGovernanceConfigMockRecorder) GetConfigWithGroup(arg0, arg1 
interface{}) *gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, 
"GetConfigWithGroup", 
reflect.TypeOf((*MockGovernanceConfig)(nil).GetConfigWithGroup), arg0, arg1)
+}
+
+// Register mocks base method.
+func (m *MockGovernanceConfig) Register(arg0 *common.URL) error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "Register", arg0)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// Register indicates an expected call of Register.
+func (mr *MockGovernanceConfigMockRecorder) Register(arg0 interface{}) 
*gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", 
reflect.TypeOf((*MockGovernanceConfig)(nil).Register), arg0)
+}
+
+// SetConfig mocks base method.
+func (m *MockGovernanceConfig) SetConfig(arg0, arg1 string) error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "SetConfig", arg0, arg1)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// SetConfig indicates an expected call of SetConfig.
+func (mr *MockGovernanceConfigMockRecorder) SetConfig(arg0, arg1 interface{}) 
*gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConfig", 
reflect.TypeOf((*MockGovernanceConfig)(nil).SetConfig), arg0, arg1)
+}
+
+// SetConfigWithGroup mocks base method.
+func (m *MockGovernanceConfig) SetConfigWithGroup(arg0, arg1, arg2 string) 
error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "SetConfigWithGroup", arg0, arg1, arg2)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// SetConfigWithGroup indicates an expected call of SetConfigWithGroup.
+func (mr *MockGovernanceConfigMockRecorder) SetConfigWithGroup(arg0, arg1, 
arg2 interface{}) *gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, 
"SetConfigWithGroup", 
reflect.TypeOf((*MockGovernanceConfig)(nil).SetConfigWithGroup), arg0, arg1, 
arg2)
+}
+
+// UnRegister mocks base method.
+func (m *MockGovernanceConfig) UnRegister(arg0 *common.URL) error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "UnRegister", arg0)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// UnRegister indicates an expected call of UnRegister.
+func (mr *MockGovernanceConfigMockRecorder) UnRegister(arg0 interface{}) 
*gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnRegister", 
reflect.TypeOf((*MockGovernanceConfig)(nil).UnRegister), arg0)
+}
diff --git a/pkg/admin/constant/const.go b/pkg/admin/constant/const.go
index 5cd0e446..ccb4cc81 100644
--- a/pkg/admin/constant/const.go
+++ b/pkg/admin/constant/const.go
@@ -17,6 +17,10 @@
 
 package constant
 
+import (
+       set "github.com/dubbogo/gost/container/set"
+)
+
 const (
        DubboPropertyKey         = "dubbo.properties"
        RegistryAddressKey       = "dubbo.registry.address"
@@ -24,37 +28,43 @@ const (
 )
 
 const (
-       AnyValue              = "*"
-       InterfaceKey          = "interface"
-       GroupKey              = "group"
-       VersionKey            = "version"
-       ClassifierKey         = "classifier"
-       CategoryKey           = "category"
-       ProvidersCategory     = "providers"
-       ConsumersCategory     = "consumers"
-       RoutersCategory       = "routers"
-       ConfiguratorsCategory = "configurators"
-       EnabledKey            = "enabled"
-       CheckKey              = "check"
-       AdminProtocol         = "admin"
-       Side                  = "side"
-       ConsumerSide          = "consumer"
-       ProviderSide          = "provider"
-       ConsumerProtocol      = "consumer"
-       EmptyProtocol         = "empty"
-       DefaultGroup          = "dubbo"
-       ApplicationKey        = "application"
-       DynamicKey            = "dynamic"
-       SerializationKey      = "serialization"
-       TimeoutKey            = "timeout"
-       DefaultTimeout        = 1000
-       WeightKey             = "weight"
-       DefaultWeight         = 100
-       OwnerKey              = "owner"
-       Service               = "service"
-       Colon                 = ":"
-       InterrogationPoint    = "?"
-       IP                    = "ip"
-       PlusSigns             = "+"
-       PunctuationPoint      = "."
+       AnyValue               = "*"
+       AnyHostValue           = "0.0.0.0"
+       InterfaceKey           = "interface"
+       GroupKey               = "group"
+       VersionKey             = "version"
+       ClassifierKey          = "classifier"
+       CategoryKey            = "category"
+       ProvidersCategory      = "providers"
+       ConsumersCategory      = "consumers"
+       RoutersCategory        = "routers"
+       ConfiguratorsCategory  = "configurators"
+       ConfiguratorRuleSuffix = ".configurators"
+       EnabledKey             = "enabled"
+       CheckKey               = "check"
+       AdminProtocol          = "admin"
+       Side                   = "side"
+       ConsumerSide           = "consumer"
+       ProviderSide           = "provider"
+       ConsumerProtocol       = "consumer"
+       EmptyProtocol          = "empty"
+       OverrideProtocol       = "override"
+       DefaultGroup           = "dubbo"
+       ApplicationKey         = "application"
+       DynamicKey             = "dynamic"
+       SerializationKey       = "serialization"
+       TimeoutKey             = "timeout"
+       DefaultTimeout         = 1000
+       WeightKey              = "weight"
+       BalancingKey           = "balancing"
+       DefaultWeight          = 100
+       OwnerKey               = "owner"
+       Service                = "service"
+       Colon                  = ":"
+       InterrogationPoint     = "?"
+       IP                     = "ip"
+       PlusSigns              = "+"
+       PunctuationPoint       = "."
 )
+
+var Configs = set.NewSet(WeightKey, BalancingKey)
diff --git a/pkg/admin/handlers/overrides.go b/pkg/admin/handlers/overrides.go
new file mode 100644
index 00000000..b278c928
--- /dev/null
+++ b/pkg/admin/handlers/overrides.go
@@ -0,0 +1,155 @@
+// 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 handlers
+
+import (
+       "net/http"
+
+       "github.com/apache/dubbo-admin/pkg/admin/config"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+       "github.com/apache/dubbo-admin/pkg/admin/services"
+       "github.com/apache/dubbo-admin/pkg/admin/util"
+       "github.com/gin-gonic/gin"
+)
+
+var overrideServiceImpl services.OverrideService = 
&services.OverrideServiceImpl{
+       GovernanceConfig: &config.GovernanceConfigImpl{},
+}
+
+func CreateOverride(c *gin.Context) {
+       var dynamicConfig *model.DynamicConfig
+       if err := c.ShouldBindJSON(&dynamicConfig); err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+
+       serviceName := dynamicConfig.Service
+       application := dynamicConfig.Application
+       if serviceName == "" && application == "" {
+               c.JSON(http.StatusBadRequest, gin.H{"error": "service or 
application must not be empty"})
+               return
+       }
+       // TODO: 
providerService.findVersionInApplication(application).equals("2.6")
+       // if application != "" && 
providerService.findVersionInApplication(application).equals("2.6") {
+       //      c.JSON(http.StatusBadRequest, errors.New("dubbo 2.6 does not 
support application scope dynamic config"))
+       //      return
+       // }
+       err := overrideServiceImpl.SaveOverride(dynamicConfig)
+       if err != nil {
+               c.JSON(http.StatusInternalServerError, gin.H{"error": 
err.Error()})
+               return
+       }
+
+       c.JSON(http.StatusCreated, true)
+}
+
+func UpdateOverride(c *gin.Context) {
+       id := c.Param("id")
+       // env := c.Param("env")
+       var dynamicConfig model.DynamicConfig
+       if err := c.ShouldBindJSON(&dynamicConfig); err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+
+       old, err := overrideServiceImpl.FindOverride(id)
+       if err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+       if old == nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": "override not 
found"})
+               return
+       }
+       overrideServiceImpl.UpdateOverride(&dynamicConfig)
+       c.JSON(http.StatusOK, true)
+}
+
+func SearchOverride(c *gin.Context) {
+       service := c.DefaultQuery("service", "")
+       application := c.DefaultQuery("application", "")
+       serviceVersion := c.DefaultQuery("serviceVersion", "")
+       serviceGroup := c.DefaultQuery("serviceGroup", "")
+
+       var override *model.DynamicConfig
+       result := make([]*model.DynamicConfig, 0)
+       var err error
+       if service != "" {
+               id := util.BuildServiceKey(service, serviceGroup, 
serviceVersion)
+               override, err = overrideServiceImpl.FindOverride(id)
+               if err != nil {
+                       c.JSON(http.StatusInternalServerError, gin.H{"error": 
err.Error()})
+                       return
+               }
+       } else if application != "" {
+               override, err = overrideServiceImpl.FindOverride(application)
+               if err != nil {
+                       c.JSON(http.StatusInternalServerError, gin.H{"error": 
err.Error()})
+                       return
+               }
+       } else {
+               c.JSON(http.StatusBadRequest, gin.H{"error": "Either Service or 
application is required."})
+               return
+       }
+       if override != nil {
+               result = append(result, override)
+       }
+       c.JSON(http.StatusOK, result)
+}
+
+func DetailOverride(c *gin.Context) {
+       id := c.Param("id")
+       override, err := overrideServiceImpl.FindOverride(id)
+       if err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+       if override == nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": "Unknown ID!"})
+               return
+       }
+       c.JSON(http.StatusOK, override)
+}
+
+func EnableOverride(c *gin.Context) {
+       id := c.Param("id")
+       err := overrideServiceImpl.EnableOverride(id)
+       if err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+       c.JSON(http.StatusOK, true)
+}
+
+func DeleteOverride(c *gin.Context) {
+       id := c.Param("id")
+       err := overrideServiceImpl.DeleteOverride(id)
+       if err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+       c.JSON(http.StatusOK, true)
+}
+
+func DisableOverride(c *gin.Context) {
+       id := c.Param("id")
+       err := overrideServiceImpl.DisableOverride(id)
+       if err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+       c.JSON(http.StatusOK, true)
+}
diff --git a/pkg/admin/model/entity.go b/pkg/admin/model/base.go
similarity index 73%
copy from pkg/admin/model/entity.go
copy to pkg/admin/model/base.go
index c2e8ce06..8673b119 100644
--- a/pkg/admin/model/entity.go
+++ b/pkg/admin/model/base.go
@@ -5,7 +5,7 @@
 // (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
+//     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,
@@ -15,7 +15,10 @@
 
 package model
 
-type Entity struct {
-       ID   string
-       Hash string
+type Base struct {
+       Application    string `json:"application"`
+       Service        string `json:"service"`
+       ID             string `json:"id"`
+       ServiceVersion string `json:"serviceVersion"`
+       ServiceGroup   string `json:"serviceGroup"`
 }
diff --git a/pkg/admin/model/dynamic_config.go 
b/pkg/admin/model/dynamic_config.go
new file mode 100644
index 00000000..ef4786b4
--- /dev/null
+++ b/pkg/admin/model/dynamic_config.go
@@ -0,0 +1,76 @@
+// 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 model
+
+import "github.com/apache/dubbo-admin/pkg/admin/constant"
+
+type DynamicConfig struct {
+       Base
+       ConfigVersion string           `json:"configVersion"`
+       Enabled       bool             `json:"enabled"`
+       Configs       []OverrideConfig `json:"configs"`
+}
+
+func (d *DynamicConfig) ToOverride() *Override {
+       o := &Override{}
+       if d.Application != "" {
+               o.Scope = constant.ApplicationKey
+               o.Key = d.Application
+       } else {
+               o.Scope = constant.Service
+               o.Key = d.Service
+       }
+       o.ConfigVersion = d.ConfigVersion
+       o.Enabled = d.Enabled
+       o.Configs = d.Configs
+
+       return o
+}
+
+func (d *DynamicConfig) ToOldOverride() []*OldOverride {
+       result := []*OldOverride{}
+       configs := d.Configs
+       for _, config := range configs {
+               if constant.Configs.Contains(config.Type) {
+                       continue
+               }
+               apps := config.Applications
+               addresses := config.Addresses
+               for _, address := range addresses {
+                       if len(apps) > 0 {
+                               for _, app := range apps {
+                                       o := &OldOverride{
+                                               Service: d.Service,
+                                               Address: address,
+                                               Enabled: d.Enabled,
+                                       }
+                                       o.SetParamsByOverrideConfig(config)
+                                       o.Application = app
+                                       result = append(result, o)
+                               }
+                       } else {
+                               o := &OldOverride{
+                                       Service: d.Service,
+                                       Address: address,
+                                       Enabled: d.Enabled,
+                               }
+                               o.SetParamsByOverrideConfig(config)
+                               result = append(result, o)
+                       }
+               }
+       }
+       return result
+}
diff --git a/pkg/admin/model/entity.go b/pkg/admin/model/entity.go
index c2e8ce06..5b06ceea 100644
--- a/pkg/admin/model/entity.go
+++ b/pkg/admin/model/entity.go
@@ -5,7 +5,7 @@
 // (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
+//     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,
@@ -15,7 +15,36 @@
 
 package model
 
+import (
+       "reflect"
+       "time"
+)
+
 type Entity struct {
-       ID   string
-       Hash string
+       Id              int64     `json:"id"`
+       Ids             []int64   `json:"ids"`
+       Hash            string    `json:"hash"`
+       Created         time.Time `json:"created"`
+       Modified        time.Time `json:"modified"`
+       Now             time.Time `json:"now"`
+       Operator        string    `json:"operator"`
+       OperatorAddress string    `json:"operatorAddress"`
+       Miss            bool      `json:"miss"`
+}
+
+func NewEntity(id int64) Entity {
+       return Entity{
+               Id: id,
+       }
+}
+
+func (e *Entity) SetOperator(operator string) {
+       if len(operator) > 200 {
+               operator = operator[:200]
+       }
+       e.Operator = operator
+}
+
+func (e *Entity) Equals(other *Entity) bool {
+       return reflect.DeepEqual(e, other)
 }
diff --git a/pkg/admin/model/match.go b/pkg/admin/model/match.go
new file mode 100644
index 00000000..f190bbea
--- /dev/null
+++ b/pkg/admin/model/match.go
@@ -0,0 +1,108 @@
+// 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 model
+
+import (
+       "regexp"
+       "strings"
+
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "github.com/apache/dubbo-admin/pkg/admin/constant"
+)
+
+type ConditionMatch struct {
+       Address     AddressMatch    `json:"address" yaml:"address"`
+       Service     ListStringMatch `json:"service" yaml:"service"`
+       Application ListStringMatch `json:"application" yaml:"application"`
+       Param       []ParamMatch    `json:"param" yaml:"param"`
+}
+
+type AddressMatch struct {
+       Wildcard string `json:"wildcard" yaml:"wildcard"`
+       Cird     string `json:"cird" yaml:"cird"`
+       Exact    string `json:"exact" yaml:"exact"`
+}
+
+func (m *AddressMatch) IsMatch(input string) bool {
+       // FIXME depends on dubbo-go/common/MatchIpExpression()
+       // if m.Cird != "" && input != "" || common.MatchIpExpression(m.Cird, 
input) {
+       if m.Cird != "" && input != "" {
+               return input == m.Cird
+       } else if m.Wildcard != "" && input != "" {
+               if constant.AnyHostValue == m.Wildcard || constant.AnyValue == 
m.Wildcard {
+                       return true
+               }
+               // FIXME depends on dubbo-go/common/IsMatchGlobPattern()
+               // return common.IsMatchGlobPattern(m.Wildcard, input)
+       } else if m.Exact != "" && input != "" {
+               return input == m.Exact
+       }
+       return false
+}
+
+type ParamMatch struct {
+       Key   string      `json:"key" yaml:"key"`
+       Value StringMatch `json:"value" yaml:"value"`
+}
+
+func (m *ParamMatch) IsMatch(url *common.URL) bool {
+       if m.Key == "" {
+               return false
+       }
+       input := url.GetParam(m.Key, "")
+       return input != "" && m.Value.IsMatch(input)
+}
+
+type ListStringMatch struct {
+       Oneof []StringMatch `json:"oneof" yaml:"oneof"`
+}
+
+func (l *ListStringMatch) IsMatch(input string) bool {
+       for _, m := range l.Oneof {
+               if m.IsMatch(input) {
+                       return true
+               }
+       }
+       return false
+}
+
+type StringMatch struct {
+       Exact    string `json:"exact" yaml:"exact"`
+       Prefix   string `json:"prefix" yaml:"prefix"`
+       Regex    string `json:"regex" yaml:"regex"`
+       Noempty  string `json:"noempty" yaml:"noempty"`
+       Empty    string `json:"empty" yaml:"empty"`
+       Wildcard string `json:"wildcard" yaml:"wildcard"`
+}
+
+func (m *StringMatch) IsMatch(input string) bool {
+       if m.Exact != "" && input != "" {
+               return input == m.Exact
+       } else if m.Prefix != "" && input != "" {
+               return strings.HasPrefix(input, m.Prefix)
+       } else if m.Regex != "" && input != "" {
+               return regexp.MustCompile(m.Regex).MatchString(input)
+       } else if m.Wildcard != "" && input != "" {
+               // only supports "*"
+               return input == m.Wildcard || constant.AnyValue == m.Wildcard
+       } else if m.Empty != "" {
+               return input == ""
+       } else if m.Noempty != "" {
+               return input != ""
+       } else {
+               return false
+       }
+}
diff --git a/pkg/admin/model/override.go b/pkg/admin/model/override.go
new file mode 100644
index 00000000..9823a4e0
--- /dev/null
+++ b/pkg/admin/model/override.go
@@ -0,0 +1,97 @@
+// 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 model
+
+import (
+       "strings"
+
+       "github.com/apache/dubbo-admin/pkg/admin/constant"
+)
+
+type Override struct {
+       Key           string           `json:"key" yaml:"key"`
+       Scope         string           `json:"scope" yaml:"scope"`
+       ConfigVersion string           `json:"configVersion" 
yaml:"configVersion"`
+       Enabled       bool             `json:"enabled" yaml:"enabled"`
+       Configs       []OverrideConfig `json:"configs" yaml:"configs"`
+}
+
+type OverrideConfig struct {
+       Side              string            `json:"side" yaml:"side"`
+       Addresses         []string          `json:"addresses" yaml:"addresses"`
+       ProviderAddresses []string          `json:"providerAddresses" 
yaml:"providerAddresses"`
+       Parameters        map[string]string `json:"parameters" 
yaml:"parameters"`
+       Applications      []string          `json:"applications" 
yaml:"applications"`
+       Services          []string          `json:"services" yaml:"services"`
+       Type              string            `json:"type" yaml:"type"`
+       Enabled           bool              `json:"enabled" yaml:"enabled"`
+       Match             ConditionMatch    `json:"match" yaml:"match"`
+}
+
+func (o *Override) ToDynamicConfig() *DynamicConfig {
+       d := &DynamicConfig{}
+       d.ConfigVersion = o.ConfigVersion
+
+       configs := make([]OverrideConfig, 0, len(o.Configs))
+       for _, c := range o.Configs {
+               if c.Type == "" {
+                       configs = append(configs, c)
+               }
+       }
+
+       if len(configs) == 0 {
+               return nil
+       }
+
+       d.Configs = configs
+
+       if o.Scope == constant.ApplicationKey {
+               d.Application = o.Key
+       } else {
+               d.Service = o.Key
+       }
+
+       d.Enabled = o.Enabled
+       return d
+}
+
+type OldOverride struct {
+       Entity
+       Service     string
+       Address     string
+       Enabled     bool
+       Application string
+       Params      string
+}
+
+func (o *OldOverride) SetParamsByOverrideConfig(config OverrideConfig) {
+       parameters := config.Parameters
+       var params strings.Builder
+
+       for key, value := range parameters {
+               param := key + "=" + value
+               params.WriteString(param)
+               params.WriteString("&")
+       }
+
+       p := params.String()
+       if p != "" {
+               if p[len(p)-1] == '&' {
+                       p = p[:len(p)-1]
+               }
+       }
+       o.Params = p
+}
diff --git a/pkg/admin/router/router.go b/pkg/admin/router/router.go
index 810551c8..609bf3b8 100644
--- a/pkg/admin/router/router.go
+++ b/pkg/admin/router/router.go
@@ -32,5 +32,16 @@ func InitRouter() *gin.Engine {
        router.GET("api/dev/service/:service", handlers.ServiceDetail)
        router.GET("/api/dev/version", handlers.Version)
 
+       override := router.Group("/api/:env/rules/override")
+       {
+               override.POST("/create", handlers.CreateOverride)
+               override.GET("/", handlers.SearchOverride)
+               override.DELETE("/:id", handlers.DeleteOverride)
+               override.GET("/:id", handlers.DetailOverride)
+               override.PUT("/enable/:id", handlers.EnableOverride)
+               override.PUT("/disable/:id", handlers.DisableOverride)
+               override.PUT("/:id", handlers.UpdateOverride)
+       }
+
        return router
 }
diff --git a/pkg/admin/model/entity.go b/pkg/admin/services/override_service.go
similarity index 64%
copy from pkg/admin/model/entity.go
copy to pkg/admin/services/override_service.go
index c2e8ce06..a0fbb6c7 100644
--- a/pkg/admin/model/entity.go
+++ b/pkg/admin/services/override_service.go
@@ -5,7 +5,7 @@
 // (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
+//     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,
@@ -13,9 +13,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package model
+package services
 
-type Entity struct {
-       ID   string
-       Hash string
+import "github.com/apache/dubbo-admin/pkg/admin/model"
+
+type OverrideService interface {
+       SaveOverride(override *model.DynamicConfig) error
+       UpdateOverride(overrideDTO *model.DynamicConfig) error
+       DisableOverride(id string) error
+       FindOverride(id string) (*model.DynamicConfig, error)
+       EnableOverride(id string) error
+       DeleteOverride(id string) error
 }
diff --git a/pkg/admin/services/override_service_impl.go 
b/pkg/admin/services/override_service_impl.go
new file mode 100644
index 00000000..b8fb13f5
--- /dev/null
+++ b/pkg/admin/services/override_service_impl.go
@@ -0,0 +1,358 @@
+// 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 services
+
+import (
+       "strings"
+
+       "dubbo.apache.org/dubbo-go/v3/common/logger"
+       "dubbo.apache.org/dubbo-go/v3/common/yaml"
+       "github.com/apache/dubbo-admin/pkg/admin/config"
+       "github.com/apache/dubbo-admin/pkg/admin/constant"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+       "github.com/apache/dubbo-admin/pkg/admin/util"
+)
+
+type OverrideServiceImpl struct {
+       GovernanceConfig config.GovernanceConfig
+}
+
+func (s *OverrideServiceImpl) SaveOverride(dynamicConfig *model.DynamicConfig) 
error {
+       key := util.BuildServiceKey(dynamicConfig.Service, 
dynamicConfig.ServiceVersion, dynamicConfig.ServiceGroup)
+       path := getPath(key)
+       existConfig, err := s.GovernanceConfig.GetConfig(path)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+
+       existOverride := dynamicConfig.ToOverride()
+       configs := []model.OverrideConfig{}
+       if existConfig != "" {
+               err = yaml.UnmarshalYML([]byte(existConfig), existOverride)
+               if err != nil {
+                       logger.Error(err)
+                       return err
+               }
+               if len(existOverride.Configs) > 0 {
+                       for _, c := range existOverride.Configs {
+                               if constant.Configs.Contains(c.Type) {
+                                       configs = append(configs, c)
+                               }
+                       }
+               }
+       }
+       configs = append(configs, dynamicConfig.Configs...)
+       existOverride.Enabled = dynamicConfig.Enabled
+       existOverride.Configs = configs
+       if b, err := yaml.MarshalYML(existOverride); err != nil {
+               logger.Error(err)
+               return err
+       } else {
+               err := s.GovernanceConfig.SetConfig(path, string(b))
+               if err != nil {
+                       logger.Error(err)
+                       return err
+               }
+       }
+
+       // for 2.6
+       if dynamicConfig.Service != "" {
+               result := dynamicConfig.ToOldOverride()
+               for _, o := range result {
+                       url, err := util.OldOverride2URL(o)
+                       if err != nil {
+                               logger.Error(err)
+                               return err
+                       }
+                       err = s.GovernanceConfig.Register(url)
+                       if err != nil {
+                               logger.Error(err)
+                               return err
+                       }
+               }
+       }
+
+       return nil
+}
+
+func getPath(key string) string {
+       key = strings.Replace(key, "/", "*", -1)
+       return key + constant.ConfiguratorRuleSuffix
+}
+
+func (s *OverrideServiceImpl) UpdateOverride(update *model.DynamicConfig) 
error {
+       key := util.BuildServiceKey(update.Service, update.ServiceGroup, 
update.ServiceVersion)
+       path := getPath(key)
+       existConfig, err := s.GovernanceConfig.GetConfig(path)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+
+       override := &model.Override{}
+       err = yaml.UnmarshalYML([]byte(existConfig), override)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+       old := override.ToDynamicConfig()
+
+       configs := make([]model.OverrideConfig, 0)
+       if len(override.Configs) > 0 {
+               for _, c := range override.Configs {
+                       if constant.Configs.Contains(c.Type) {
+                               configs = append(configs, c)
+                       }
+               }
+       }
+       configs = append(configs, update.Configs...)
+       override.Configs = configs
+       override.Enabled = update.Enabled
+       if b, err := yaml.MarshalYML(override); err != nil {
+               logger.Error(err)
+               return err
+       } else {
+               err := s.GovernanceConfig.SetConfig(path, string(b))
+               if err != nil {
+                       logger.Error(err)
+                       return err
+               }
+       }
+
+       // for 2.6
+       if update.Service != "" {
+               oldOverrides := old.ToOldOverride()
+               updatedOverrides := update.ToOldOverride()
+               for _, o := range oldOverrides {
+                       url, err := util.OldOverride2URL(o)
+                       if err != nil {
+                               return err
+                       }
+                       s.GovernanceConfig.UnRegister(url)
+               }
+               for _, o := range updatedOverrides {
+                       url, err := util.OldOverride2URL(o)
+                       if err != nil {
+                               return err
+                       }
+                       s.GovernanceConfig.Register(url)
+               }
+       }
+
+       return nil
+}
+
+func (s *OverrideServiceImpl) DisableOverride(key string) error {
+       path := getPath(key)
+
+       conf, err := s.GovernanceConfig.GetConfig(path)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+
+       override := &model.Override{}
+       err = yaml.UnmarshalYML([]byte(conf), override)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+       old := override.ToDynamicConfig()
+       override.Enabled = false
+
+       if b, err := yaml.MarshalYML(override); err != nil {
+               logger.Error(err)
+               return err
+       } else {
+               err := s.GovernanceConfig.SetConfig(path, string(b))
+               if err != nil {
+                       return err
+               }
+       }
+
+       // for 2.6
+       if override.Scope == constant.Service {
+               overrides := old.ToOldOverride()
+               for _, o := range overrides {
+                       o.Enabled = true
+                       url, err := util.OldOverride2URL(o)
+                       if err != nil {
+                               logger.Error(err)
+                               return err
+                       }
+                       s.GovernanceConfig.UnRegister(url)
+
+                       o.Enabled = false
+                       url, err = util.OldOverride2URL(o)
+                       if err != nil {
+                               logger.Error(err)
+                               return err
+                       }
+                       s.GovernanceConfig.Register(url)
+               }
+       }
+
+       return nil
+}
+
+func (s *OverrideServiceImpl) FindOverride(key string) (*model.DynamicConfig, 
error) {
+       path := getPath(key)
+       conf, err := s.GovernanceConfig.GetConfig(path)
+       if err != nil {
+               logger.Error(err)
+               return nil, err
+       }
+
+       if conf != "" {
+               override := &model.Override{}
+               err := yaml.UnmarshalYML([]byte(conf), override)
+               if err != nil {
+                       logger.Error(err)
+                       return nil, err
+               }
+
+               dynamicConfig := override.ToDynamicConfig()
+               if dynamicConfig != nil {
+                       dynamicConfig.ID = key
+                       if constant.Service == override.Scope {
+                               dynamicConfig.Service = util.GetInterface(key)
+                               dynamicConfig.ServiceGroup = util.GetGroup(key)
+                               dynamicConfig.ServiceVersion = 
util.GetVersion(key)
+                       }
+               }
+               return dynamicConfig, nil
+       }
+
+       return nil, nil
+}
+
+func (s *OverrideServiceImpl) EnableOverride(key string) error {
+       path := getPath(key)
+       conf, err := s.GovernanceConfig.GetConfig(path)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+
+       override := &model.Override{}
+       err = yaml.UnmarshalYML([]byte(conf), override)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+
+       old := override.ToDynamicConfig()
+       override.Enabled = true
+       if b, err := yaml.MarshalYML(override); err != nil {
+               logger.Error(err)
+               return err
+       } else {
+               err := s.GovernanceConfig.SetConfig(path, string(b))
+               if err != nil {
+                       logger.Error(err)
+                       return err
+               }
+       }
+
+       // for 2.6
+       if override.Scope == constant.Service {
+               overrides := old.ToOldOverride()
+               for _, o := range overrides {
+                       o.Enabled = false
+                       url, err := util.OldOverride2URL(o)
+                       if err != nil {
+                               return err
+                       }
+                       s.GovernanceConfig.UnRegister(url)
+
+                       o.Enabled = true
+                       url, err = util.OldOverride2URL(o)
+                       if err != nil {
+                               return err
+                       }
+                       s.GovernanceConfig.Register(url)
+               }
+       }
+
+       return nil
+}
+
+func (s *OverrideServiceImpl) DeleteOverride(key string) error {
+       path := getPath(key)
+       conf, err := s.GovernanceConfig.GetConfig(path)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+
+       override := &model.Override{}
+       err = yaml.UnmarshalYML([]byte(conf), override)
+       if err != nil {
+               logger.Error(err)
+               return err
+       }
+       old := override.ToDynamicConfig()
+
+       if len(override.Configs) > 0 {
+               newConfigs := make([]model.OverrideConfig, 0)
+               for _, c := range override.Configs {
+                       if constant.Configs.Contains(c.Type) {
+                               newConfigs = append(newConfigs, c)
+                       }
+               }
+               if len(newConfigs) == 0 {
+                       err := s.GovernanceConfig.DeleteConfig(path)
+                       if err != nil {
+                               logger.Error(err)
+                               return err
+                       }
+               } else {
+                       override.Configs = newConfigs
+                       if b, err := yaml.MarshalYML(override); err != nil {
+                               logger.Error(err)
+                               return err
+                       } else {
+                               err := s.GovernanceConfig.SetConfig(path, 
string(b))
+                               if err != nil {
+                                       logger.Error(err)
+                                       return err
+                               }
+                       }
+               }
+       } else {
+               err := s.GovernanceConfig.DeleteConfig(path)
+               if err != nil {
+                       logger.Error(err)
+                       return err
+               }
+       }
+
+       // for 2.6
+       if override.Scope == constant.Service {
+               overrides := old.ToOldOverride()
+               for _, o := range overrides {
+                       url, err := util.OldOverride2URL(o)
+                       if err != nil {
+                               logger.Error(err)
+                               return err
+                       }
+                       s.GovernanceConfig.UnRegister(url)
+               }
+       }
+
+       return nil
+}
diff --git a/pkg/admin/services/override_service_impl_test.go 
b/pkg/admin/services/override_service_impl_test.go
new file mode 100644
index 00000000..a3b2446b
--- /dev/null
+++ b/pkg/admin/services/override_service_impl_test.go
@@ -0,0 +1,332 @@
+// 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 services
+
+import (
+       "reflect"
+       "testing"
+
+       "github.com/apache/dubbo-admin/pkg/admin/config"
+       "github.com/apache/dubbo-admin/pkg/admin/constant"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+       "github.com/golang/mock/gomock"
+)
+
+func TestOverrideServiceImpl_SaveOverride(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockGovernanceConfig := config.NewMockGovernanceConfig(ctrl)
+       mockGovernanceConfig.EXPECT().SetConfig(gomock.Any(), 
gomock.Any()).Return(nil)
+       mockGovernanceConfig.EXPECT().GetConfig(gomock.Any()).Return("", nil)
+       mockGovernanceConfig.EXPECT().Register(gomock.Any()).Return(nil)
+
+       type args struct {
+               dynamicConfig *model.DynamicConfig
+       }
+       tests := []struct {
+               name    string
+               s       OverrideService
+               args    args
+               wantErr bool
+       }{
+               {
+                       name: "TestOK",
+                       s: &OverrideServiceImpl{
+                               GovernanceConfig: mockGovernanceConfig,
+                       },
+                       args: args{
+                               dynamicConfig: &model.DynamicConfig{
+                                       Base: model.Base{
+                                               Application:    "",
+                                               Service:        "testService",
+                                               ServiceGroup:   "testGroup",
+                                               ServiceVersion: "1.2.3",
+                                       },
+                                       Enabled:       true,
+                                       ConfigVersion: "v2.7",
+                                       Configs: []model.OverrideConfig{
+                                               {
+                                                       Addresses: 
[]string{"0.0.0.0"},
+                                                       Parameters: 
map[string]string{
+                                                               "timeout": 
"1000",
+                                                       },
+                                                       Side: "consumer",
+                                               },
+                                       },
+                               },
+                       },
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if err := tt.s.SaveOverride(tt.args.dynamicConfig); 
(err != nil) != tt.wantErr {
+                               t.Errorf("OverrideServiceImpl.SaveOverride() 
error = %v, wantErr %v", err, tt.wantErr)
+                       }
+               })
+       }
+}
+
+func TestOverrideServiceImpl_UpdateOverride(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockGovernanceConfig := config.NewMockGovernanceConfig(ctrl)
+       mockGovernanceConfig.EXPECT().SetConfig(gomock.Any(), 
gomock.Any()).Return(nil)
+       
mockGovernanceConfig.EXPECT().GetConfig(getPath("testGroup/testService:testVersion")).Return("configVersion:
 v2.7\nconfigs:\n- addresses:\n  - 0.0.0.0\n  enabled: false\n  parameters:\n   
 timeout: 6000\n  side: consumer\nenabled: true\nkey: testService\nscope: 
service\n", nil)
+       mockGovernanceConfig.EXPECT().Register(gomock.Any()).Return(nil)
+       mockGovernanceConfig.EXPECT().UnRegister(gomock.Any()).Return(nil)
+
+       type args struct {
+               update *model.DynamicConfig
+       }
+       tests := []struct {
+               name    string
+               s       *OverrideServiceImpl
+               args    args
+               wantErr bool
+       }{
+               {
+                       name: "TestOK",
+                       s: &OverrideServiceImpl{
+                               GovernanceConfig: mockGovernanceConfig,
+                       },
+                       args: args{
+                               update: &model.DynamicConfig{
+                                       Base: model.Base{
+                                               Application:    "",
+                                               Service:        "testService",
+                                               ServiceGroup:   "testGroup",
+                                               ServiceVersion: "testVersion",
+                                       },
+                                       Enabled:       true,
+                                       ConfigVersion: "v2.7",
+                                       Configs: []model.OverrideConfig{
+                                               {
+                                                       Addresses: 
[]string{"0.0.0.0"},
+                                                       Parameters: 
map[string]string{
+                                                               "timeout": 
"1000",
+                                                       },
+                                                       Side: "consumer",
+                                               },
+                                       },
+                               },
+                       },
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if err := tt.s.UpdateOverride(tt.args.update); (err != 
nil) != tt.wantErr {
+                               t.Errorf("OverrideServiceImpl.UpdateOverride() 
error = %v, wantErr %v", err, tt.wantErr)
+                       }
+               })
+       }
+}
+
+func TestOverrideServiceImpl_FindOverride(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockGovernanceConfig := config.NewMockGovernanceConfig(ctrl)
+       
mockGovernanceConfig.EXPECT().GetConfig(getPath("testGroup/testService:testVersion")).Return("configVersion:
 v2.7\nconfigs:\n- addresses:\n  - 0.0.0.0\n  enabled: false\n  parameters:\n   
 timeout: 6000\n  side: consumer\nenabled: true\nkey: testService\nscope: 
service\n", nil)
+
+       type args struct {
+               key string
+       }
+       tests := []struct {
+               name    string
+               s       *OverrideServiceImpl
+               args    args
+               want    *model.DynamicConfig
+               wantErr bool
+       }{
+               {
+                       name: "TestOK",
+                       s: &OverrideServiceImpl{
+                               GovernanceConfig: mockGovernanceConfig,
+                       },
+                       args: args{
+                               key: "testGroup/testService:testVersion",
+                       },
+                       want: &model.DynamicConfig{
+                               Base: model.Base{
+                                       ID:             
"testGroup/testService:testVersion",
+                                       Service:        "testService",
+                                       ServiceGroup:   "testGroup",
+                                       ServiceVersion: "testVersion",
+                               },
+                               ConfigVersion: "v2.7",
+                               Enabled:       true,
+                               Configs: []model.OverrideConfig{
+                                       {
+                                               Addresses: []string{"0.0.0.0"},
+                                               Parameters: map[string]string{
+                                                       "timeout": "6000",
+                                               },
+                                               Enabled: false,
+                                               Side:    "consumer",
+                                       },
+                               },
+                       },
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got, err := tt.s.FindOverride(tt.args.key)
+                       if (err != nil) != tt.wantErr {
+                               t.Errorf("OverrideServiceImpl.FindOverride() 
error = %v, wantErr %v", err, tt.wantErr)
+                               return
+                       }
+                       if !reflect.DeepEqual(got, tt.want) {
+                               t.Errorf("OverrideServiceImpl.FindOverride() = 
%+v, want %+v", got, tt.want)
+                       }
+               })
+       }
+}
+
+func TestOverrideServiceImpl_DeleteOverride(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockGovernanceConfig := config.NewMockGovernanceConfig(ctrl)
+       
mockGovernanceConfig.EXPECT().GetConfig(getPath("testGroup/testService:testVersion")).Return("configVersion:
 v2.7\nconfigs:\n- addresses:\n  - 0.0.0.0\n  enabled: false\n  parameters:\n   
 timeout: 6000\n  side: consumer\nenabled: true\nkey: testService\nscope: 
service\n", nil)
+       
mockGovernanceConfig.EXPECT().DeleteConfig(getPath("testGroup/testService:testVersion")).Return(nil)
+       mockGovernanceConfig.EXPECT().UnRegister(gomock.Any()).Return(nil)
+
+       type args struct {
+               key string
+       }
+       tests := []struct {
+               name    string
+               s       *OverrideServiceImpl
+               args    args
+               wantErr bool
+       }{
+               {
+                       name: "TestOK",
+                       s: &OverrideServiceImpl{
+                               GovernanceConfig: mockGovernanceConfig,
+                       },
+                       args: args{
+                               key: "testGroup/testService:testVersion",
+                       },
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if err := tt.s.DeleteOverride(tt.args.key); (err != 
nil) != tt.wantErr {
+                               t.Errorf("OverrideServiceImpl.DeleteOverride() 
error = %v, wantErr %v", err, tt.wantErr)
+                       }
+               })
+       }
+}
+
+func TestOverrideServiceImpl_EnableOverride(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockGovernanceConfig := config.NewMockGovernanceConfig(ctrl)
+       
mockGovernanceConfig.EXPECT().GetConfig(getPath("testGroup/testService:testVersion")).Return("configVersion:
 v2.7\nconfigs:\n- addresses:\n  - 0.0.0.0\n  enabled: false\n  parameters:\n   
 timeout: 6000\n  side: consumer\nenabled: true\nkey: testService\nscope: 
service\n", nil)
+       
mockGovernanceConfig.EXPECT().SetConfig(getPath("testGroup/testService:testVersion"),
 gomock.Any()).Return(nil)
+       mockGovernanceConfig.EXPECT().Register(gomock.Any()).Return(nil)
+       mockGovernanceConfig.EXPECT().UnRegister(gomock.Any()).Return(nil)
+
+       type args struct {
+               key string
+       }
+       tests := []struct {
+               name    string
+               s       *OverrideServiceImpl
+               args    args
+               wantErr bool
+       }{
+               {
+                       name: "TestOK",
+                       s: &OverrideServiceImpl{
+                               GovernanceConfig: mockGovernanceConfig,
+                       },
+                       args: args{
+                               key: "testGroup/testService:testVersion",
+                       },
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if err := tt.s.EnableOverride(tt.args.key); (err != 
nil) != tt.wantErr {
+                               t.Errorf("OverrideServiceImpl.EnableOverride() 
error = %v, wantErr %v", err, tt.wantErr)
+                       }
+               })
+       }
+}
+
+func TestOverrideServiceImpl_DisableOverride(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockGovernanceConfig := config.NewMockGovernanceConfig(ctrl)
+       
mockGovernanceConfig.EXPECT().GetConfig(getPath("testGroup/testService:testVersion")).Return("configVersion:
 v2.7\nconfigs:\n- addresses:\n  - 0.0.0.0\n  enabled: false\n  parameters:\n   
 timeout: 6000\n  side: consumer\nenabled: true\nkey: testService\nscope: 
service\n", nil)
+       
mockGovernanceConfig.EXPECT().SetConfig(getPath("testGroup/testService:testVersion"),
 gomock.Any()).Return(nil)
+       mockGovernanceConfig.EXPECT().Register(gomock.Any()).Return(nil)
+       mockGovernanceConfig.EXPECT().UnRegister(gomock.Any()).Return(nil)
+
+       type args struct {
+               key string
+       }
+       tests := []struct {
+               name    string
+               s       *OverrideServiceImpl
+               args    args
+               wantErr bool
+       }{
+               {
+                       name: "TestOK",
+                       s: &OverrideServiceImpl{
+                               GovernanceConfig: mockGovernanceConfig,
+                       },
+                       args: args{
+                               key: "testGroup/testService:testVersion",
+                       },
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if err := tt.s.DisableOverride(tt.args.key); (err != 
nil) != tt.wantErr {
+                               t.Errorf("OverrideServiceImpl.DisableOverride() 
error = %v, wantErr %v", err, tt.wantErr)
+                       }
+               })
+       }
+}
+
+func Test_getPath(t *testing.T) {
+       type args struct {
+               key string
+       }
+       tests := []struct {
+               name string
+               args args
+               want string
+       }{
+               {
+                       name: "TestOK",
+                       args: args{
+                               key: "testGroup/testService:testVersion",
+                       },
+                       want: "testGroup*testService:testVersion" + 
constant.ConfiguratorRuleSuffix,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := getPath(tt.args.key); got != tt.want {
+                               t.Errorf("getPath() = %v, want %v", got, 
tt.want)
+                       }
+               })
+       }
+}
diff --git a/pkg/admin/util/override_utils.go b/pkg/admin/util/override_utils.go
new file mode 100644
index 00000000..90e0736d
--- /dev/null
+++ b/pkg/admin/util/override_utils.go
@@ -0,0 +1,59 @@
+// 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 util
+
+import (
+       "net/url"
+       "strconv"
+       "strings"
+
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "github.com/apache/dubbo-admin/pkg/admin/constant"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+)
+
+func OldOverride2URL(o *model.OldOverride) (*common.URL, error) {
+       group := GetGroup(o.Service)
+       version := GetVersion(o.Service)
+       interfaceName := GetInterface(o.Service)
+       var sb strings.Builder
+       sb.WriteString(constant.OverrideProtocol)
+       sb.WriteString("://")
+       if o.Address != "" && o.Address != constant.AnyValue {
+               sb.WriteString(o.Address)
+       } else {
+               sb.WriteString(constant.AnyHostValue)
+       }
+       sb.WriteString("/")
+       sb.WriteString(interfaceName)
+       sb.WriteString("?")
+       params, _ := url.ParseQuery(o.Params)
+       params.Set(constant.CategoryKey, constant.ConfiguratorsCategory)
+       params.Set(constant.EnabledKey, strconv.FormatBool(o.Enabled))
+       params.Set(constant.DynamicKey, "false")
+       if o.Application != "" && o.Application != constant.AnyValue {
+               params.Set(constant.ApplicationKey, o.Application)
+       }
+       if group != "" {
+               params.Set(constant.GroupKey, group)
+       }
+       if version != "" {
+               params.Set(constant.VersionKey, version)
+       }
+       sb.WriteString(params.Encode())
+
+       return common.NewURL(sb.String())
+}
diff --git a/pkg/admin/util/override_utils_test.go 
b/pkg/admin/util/override_utils_test.go
new file mode 100644
index 00000000..6812b5e0
--- /dev/null
+++ b/pkg/admin/util/override_utils_test.go
@@ -0,0 +1,74 @@
+// 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 util
+
+import (
+       "reflect"
+       "testing"
+
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "github.com/apache/dubbo-admin/pkg/admin/constant"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+)
+
+func TestOldOverride2URL(t *testing.T) {
+       type args struct {
+               o *model.OldOverride
+       }
+       tests := []struct {
+               name    string
+               args    args
+               want    *common.URL
+               wantErr bool
+       }{
+               {
+                       name: "RightTest",
+                       args: args{
+                               o: &model.OldOverride{
+                                       Service:     "group/service:1.0.0",
+                                       Address:     "192.168.1.1:8080",
+                                       Enabled:     true,
+                                       Application: "app",
+                               },
+                       },
+                       want: common.NewURLWithOptions(
+                               common.WithProtocol(constant.OverrideProtocol),
+                               common.WithIp("192.168.1.1"),
+                               common.WithPort("8080"),
+                               common.WithPath("service"),
+                               common.WithParamsValue(constant.CategoryKey, 
constant.ConfiguratorsCategory),
+                               common.WithParamsValue(constant.EnabledKey, 
"true"),
+                               common.WithParamsValue(constant.DynamicKey, 
"false"),
+                               common.WithParamsValue(constant.ApplicationKey, 
"app"),
+                               common.WithParamsValue(constant.GroupKey, 
"group"),
+                               common.WithParamsValue(constant.VersionKey, 
"1.0.0"),
+                       ),
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got, err := OldOverride2URL(tt.args.o)
+                       if (err != nil) != tt.wantErr {
+                               t.Errorf("OldOverride2URL() error = %v, wantErr 
%v", err, tt.wantErr)
+                               return
+                       }
+                       if !reflect.DeepEqual(got.String(), tt.want.String()) {
+                               t.Errorf("OldOverride2URL() = %v, want %v", 
got, tt.want)
+                       }
+               })
+       }
+}

Reply via email to