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)
+ }
+ })
+ }
+}