Skip to content
Snippets Groups Projects
Commit 7320d653 authored by Laurence's avatar Laurence Committed by GitHub
Browse files

Merge branch '3.0' into 3.0

parents 7521e216 d7b6a2c3
No related branches found
No related tags found
No related merge requests found
Showing
with 104 additions and 63 deletions
......@@ -15,7 +15,7 @@ jobs:
# If you want to matrix build , you can append the following list.
matrix:
go_version:
- 1.13
- 1.15
os:
- ubuntu-latest
......@@ -56,7 +56,7 @@ jobs:
- name: gofmt
run: |
go fmt ./... && git checkout -- go.mod && git status && [[ -z `git status -s` ]]
go fmt ./... && git status && [[ -z `git status -s` ]]
# diff -u <(echo -n) <(gofmt -d -s .)
- name: Install go ci lint
......@@ -69,9 +69,9 @@ jobs:
run: |
make verify
# - name: Integrate Test
# run: |
# chmod +x integrate_test.sh && ./integrate_test.sh ${{github.event.pull_request.head.repo.full_name}} ${{github.event.pull_request.head.sha}}
- name: Integrate Test
run: |
chmod +x integrate_test.sh && ./integrate_test.sh ${{github.event.pull_request.head.repo.full_name}} ${{github.event.pull_request.head.sha}}
- name: Post Coverage
run: bash <(curl -s https://codecov.io/bash)
......
[submodule "samples"]
path = samples
url = git@github.com:apache/dubbo-go-samples.git
......@@ -6,12 +6,17 @@
- [Add dubbo-go-cli telnet tool](https://github.com/apache/dubbo-go/pull/818)
- [Add Prox ImplementFunc to allow override impl](https://github.com/apache/dubbo-go/pull/1019)
- [Add read configuration path from the command line when start](https://github.com/apache/dubbo-go/pull/1039)
- [Add use invoker with same ip as client first](https://github.com/apache/dubbo-go/pull/1023)
- [Add an "api way" to set general configure](https://github.com/apache/dubbo-go/pull/1020)
- [Add registry ip:port set from enviroment variable](https://github.com/apache/dubbo-go/pull/1036)
### Enhancement
- [introduce ConfigPostProcessor extension](https://github.com/apache/dubbo-go/pull/943)
- [Impl extension of two urls comparison](https://github.com/apache/dubbo-go/pull/854)
- [using event-driven to let router send signal to notify channel](https://github.com/apache/dubbo-go/pull/976)
- [lint codes](https://github.com/apache/dubbo-go/pull/941)
- [Imp: destroy invoker smoothly](https://github.com/apache/dubbo-go/pull/1045)
- [Improve config center](https://github.com/apache/dubbo-go/pull/1030)
### Bugfixes
- [Fix: generic struct2MapAll key of map keep type](https://github.com/apache/dubbo-go/pull/928)
......@@ -23,8 +28,21 @@
- [Fix: etcd exit panic](https://github.com/apache/dubbo-go/pull/1013)
- [Fix: when connect to provider fail, will occur panic](https://github.com/apache/dubbo-go/pull/1021)
- [Fix: support getty send Length, when the data transfer failed](https://github.com/apache/dubbo-go/pull/1028)
- [Fix: RPCInvocation.ServiceKey use PATH_KEY instead of INTERFACE_KEY ](https://github.com/apache/dubbo-go/pull/1078/files)
- [Fix: zk too many tcp conn](https://github.com/apache/dubbo-go/pull/1010)
- [Fix: fix zk listener func pathToKey](https://github.com/apache/dubbo-go/pull/1066)
- [Fix: graceful shutdown](https://github.com/apache/dubbo-go/pull/1007)
- [Fix: nacos service provider does not require subscribe](https://github.com/apache/dubbo-go/pull/1056)
- [Fix: key of generic map convert is more general](https://github.com/apache/dubbo-go/pull/1041)
- [Fix: body buffer too short](https://github.com/apache/dubbo-go/pull/1090)
Milestone: [https://github.com/apache/dubbo-go/milestone/7](https://github.com/apache/dubbo-go/milestone/7?closed=1)
### Dependencies
- [Bump dubbo-go-hessian2 from v1.9.0-rc1 to v1.9.1](https://github.com/apache/dubbo-go/pull/1088/files)
- [Bump github.com/nacos-group/nacos-sdk-go from 1.0.5 to v1.0.7](https://github.com/apache/dubbo-go/pull/1106)
Milestone:
- [https://github.com/apache/dubbo-go/milestone/7](https://github.com/apache/dubbo-go/milestone/7?closed=1)
- [https://github.com/apache/dubbo-go/milestone/10](https://github.com/apache/dubbo-go/milestone/10?closed=1)
## 1.5.5
......
......@@ -73,6 +73,7 @@ Finished List:
- Router
* [Condition router](https://github.com/apache/dubbo-go/pull/294)
* [Health check router](https://github.com/apache/dubbo-go/pull/389)
* [Dynamic_tag_router](https://github.com/apache/dubbo-go/pull/703)
- Registry
* ZooKeeper
......@@ -134,6 +135,10 @@ Finished List:
* [Nacos](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/nacos/service_discovery.go)
* [Zookeeper](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/zookeeper/service_discovery.go)
* [Etcd](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/etcdv3/service_discovery.go)
* [File](https://github.com/apache/dubbo-go/pull/732)
- Tool
* [Dubbo-go-cli](https://github.com/apache/dubbo-go/pull/818)
You can know more about dubbo-go by its [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap).
......@@ -177,7 +182,7 @@ If you are willing to do some code contributions and document contributions to [
## Community
If u want to communicate with our community, pls scan the following [dubbobo Ding-Ding QR code](https://mmbiz.qpic.cn/mmbiz_jpg/yvBJb5IiafvnHVBdtia30dxA2hKotr9DEckWsZ7aOJcDWDaSVMGwLmYv8GRgIQtqb4C2svicp8nVkMmGy7yKC5tyA/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) or search our commnity DingDing group code 31363295.
If u want to communicate with our community, pls scan the following dubbobo DingDing QR code or search our commnity DingDing group code 31363295.
<div>
<table>
......
......@@ -72,6 +72,7 @@ Apache License, Version 2.0
- 路由器
* [Condition router](https://github.com/apache/dubbo-go/pull/294)
* [Health check router](https://github.com/apache/dubbo-go/pull/389)
* [Dynamic_tag_router](https://github.com/apache/dubbo-go/pull/703)
- 注册中心
* ZooKeeper
......@@ -133,6 +134,10 @@ Apache License, Version 2.0
* [Nacos](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/nacos/service_discovery.go)
* [Zookeeper](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/zookeeper/service_discovery.go)
* [Etcd](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/etcdv3/service_discovery.go)
* [File](https://github.com/apache/dubbo-go/pull/732)
- 工具箱
* [Dubbo-go-cli](https://github.com/apache/dubbo-go/pull/818)
你可以通过访问 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 知道更多关于 dubbo-go 的信息。
......@@ -194,7 +199,22 @@ make test
</table>
</div>
如果想加入到社区微信群,可以先添加社区负责人 于雨 的微信 AlexanderStocks 。添加微信之前,请先给 dubbo-go 点 star 作为对项目的支持,添加好友时请报上 github ID 以进行验证。
dubbogo 社区已经开通微信公众号 "dubbogo大区",可在微信搜索 "dubbogo大区" 或者扫描如下二维码关注,可通过公众号私信留言加入 dubbogo 微信社区。
<div>
<table>
<tbody>
<tr></tr>
<tr>
<td align="center" valign="middle">
<img width="80px" height="115px" src="./doc/pic/misc/dubbogo-wechat.png">
</a>
</td>
</tr>
<tr></tr>
</tbody>
</table>
</div>
作为一个维护已经帮助构建了经受多家大型微服务系统的社区,我们足以为现有的成绩感到自豪。社区欢迎能提出建设性意见者,只知索取者和喷子请绕行。
......
......@@ -40,10 +40,8 @@ import (
"github.com/apache/dubbo-go/protocol/mock"
)
var (
availableUrl, _ = common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider",
constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
)
var availableUrl, _ = common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider",
constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
func registerAvailable(invoker *mock.MockInvoker) protocol.Invoker {
extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
......
......@@ -56,7 +56,7 @@ func (invoker *baseClusterInvoker) GetUrl() *common.URL {
}
func (invoker *baseClusterInvoker) Destroy() {
//this is must atom operation
// this is must atom operation
if invoker.destroyed.CAS(false, true) {
invoker.directory.Destroy()
}
......@@ -69,7 +69,7 @@ func (invoker *baseClusterInvoker) IsAvailable() bool {
return invoker.directory.IsAvailable()
}
//check invokers availables
// check invokers availables
func (invoker *baseClusterInvoker) checkInvokers(invokers []protocol.Invoker, invocation protocol.Invocation) error {
if len(invokers) == 0 {
ip := common.GetLocalIp()
......@@ -78,10 +78,9 @@ func (invoker *baseClusterInvoker) checkInvokers(invokers []protocol.Invoker, in
invocation.MethodName(), invoker.directory.GetUrl().SubURL.Key(), invoker.directory.GetUrl().String(), ip, constant.Version)
}
return nil
}
//check cluster invoker is destroyed or not
// check cluster invoker is destroyed or not
func (invoker *baseClusterInvoker) checkWhetherDestroyed() error {
if invoker.destroyed.Load() {
ip := common.GetLocalIp()
......@@ -99,7 +98,7 @@ func (invoker *baseClusterInvoker) doSelect(lb cluster.LoadBalance, invocation p
url := invokers[0].GetUrl()
sticky := url.GetParamBool(constant.STICKY_KEY, false)
//Get the service method sticky config if have
// Get the service method sticky config if have
sticky = url.GetMethodParamBool(invocation.MethodName(), constant.STICKY_KEY, sticky)
if invoker.stickyInvoker != nil && !isInvoked(invoker.stickyInvoker, invokers) {
......@@ -135,7 +134,7 @@ func (invoker *baseClusterInvoker) doSelectInvoker(lb cluster.LoadBalance, invoc
selectedInvoker := lb.Select(invokers, invocation)
//judge if the selected Invoker is invoked and available
// judge if the selected Invoker is invoked and available
if (!selectedInvoker.IsAvailable() && invoker.availablecheck) || isInvoked(selectedInvoker, invoked) {
protocol.SetInvokerUnhealthyStatus(selectedInvoker)
otherInvokers := getOtherInvokers(invokers, selectedInvoker)
......@@ -193,10 +192,10 @@ func getLoadBalance(invoker protocol.Invoker, invocation protocol.Invocation) cl
url := invoker.GetUrl()
methodName := invocation.MethodName()
//Get the service loadbalance config
// Get the service loadbalance config
lb := url.GetParam(constant.LOADBALANCE_KEY, constant.DEFAULT_LOADBALANCE)
//Get the service method loadbalance config if have
// Get the service method loadbalance config if have
if v := url.GetMethodParam(methodName, constant.LOADBALANCE_KEY, ""); len(v) > 0 {
lb = v
}
......
......@@ -20,6 +20,7 @@ package cluster_impl
import (
"context"
)
import (
"github.com/apache/dubbo-go/cluster"
"github.com/apache/dubbo-go/common/logger"
......
......@@ -40,10 +40,8 @@ import (
"github.com/apache/dubbo-go/protocol/mock"
)
var (
broadcastUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
)
var broadcastUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
func registerBroadcast(mockInvokers ...*mock.MockInvoker) protocol.Invoker {
extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance)
......
......@@ -137,10 +137,10 @@ func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation pr
return &protocol.RPCResult{}
}
//Get the service loadbalance config
// Get the service loadbalance config
url := invokers[0].GetUrl()
lb := url.GetParam(constant.LOADBALANCE_KEY, constant.DEFAULT_LOADBALANCE)
//Get the service method loadbalance config if have
// Get the service method loadbalance config if have
methodName := invocation.MethodName()
if v := url.GetMethodParam(methodName, constant.LOADBALANCE_KEY, ""); v != "" {
lb = v
......@@ -149,7 +149,7 @@ func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation pr
loadBalance := extension.GetLoadbalance(lb)
invoked := make([]protocol.Invoker, 0, len(invokers))
ivk := invoker.doSelect(loadBalance, invocation, invokers, invoked)
//DO INVOKE
// DO INVOKE
result := ivk.Invoke(ctx, invocation)
if result.Error() != nil {
invoker.once.Do(func() {
......
......@@ -42,10 +42,8 @@ import (
"github.com/apache/dubbo-go/protocol/mock"
)
var (
failbackUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
)
var failbackUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
// registerFailback register failbackCluster to cluster extension.
func registerFailback(invoker *mock.MockInvoker) protocol.Invoker {
......
......@@ -20,6 +20,7 @@ package cluster_impl
import (
"context"
)
import (
"github.com/apache/dubbo-go/cluster"
"github.com/apache/dubbo-go/protocol"
......
......@@ -40,10 +40,8 @@ import (
"github.com/apache/dubbo-go/protocol/mock"
)
var (
failfastUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
)
var failfastUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
// registerFailfast register failfastCluster to cluster extension.
func registerFailfast(invoker *mock.MockInvoker) protocol.Invoker {
......
......@@ -64,8 +64,8 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr
loadBalance := getLoadBalance(invokers[0], invocation)
for i := 0; i <= retries; i++ {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
// Reselect before retry to avoid a change of candidate `invokers`.
// NOTE: if `invokers` changed, then `invoked` also lose accuracy.
if i > 0 {
if err := invoker.checkWhetherDestroyed(); err != nil {
return &protocol.RPCResult{Err: err}
......@@ -81,7 +81,7 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr
continue
}
invoked = append(invoked, ivk)
//DO INVOKE
// DO INVOKE
result = ivk.Invoke(ctx, invocation)
if result.Error() != nil {
providers = append(providers, ivk.GetUrl().Key())
......@@ -105,7 +105,8 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr
"Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. "+
"Last error is %+v.", methodName, invokerSvc, retries, providers, len(providers), len(invokers),
invokerUrl, ip, constant.Version, result.Error().Error()),
)}
),
}
}
func getRetries(invokers []protocol.Invoker, methodName string) int {
......@@ -114,9 +115,9 @@ func getRetries(invokers []protocol.Invoker, methodName string) int {
}
url := invokers[0].GetUrl()
//get reties
// get reties
retriesConfig := url.GetParam(constant.RETRIES_KEY, constant.DEFAULT_RETRIES)
//Get the service method loadbalance config if have
// Get the service method loadbalance config if have
if v := url.GetMethodParam(methodName, constant.RETRIES_KEY, ""); len(v) != 0 {
retriesConfig = v
}
......
......@@ -23,6 +23,7 @@ import (
"net/url"
"testing"
)
import (
perrors "github.com/pkg/errors"
"github.com/stretchr/testify/assert"
......
......@@ -20,6 +20,7 @@ package cluster_impl
import (
"context"
)
import (
"github.com/apache/dubbo-go/cluster"
"github.com/apache/dubbo-go/common/constant"
......@@ -56,9 +57,9 @@ func (invoker *failsafeClusterInvoker) Invoke(ctx context.Context, invocation pr
url := invokers[0].GetUrl()
methodName := invocation.MethodName()
//Get the service loadbalance config
// Get the service loadbalance config
lb := url.GetParam(constant.LOADBALANCE_KEY, constant.DEFAULT_LOADBALANCE)
//Get the service method loadbalance config if have
// Get the service method loadbalance config if have
if v := url.GetMethodParam(methodName, constant.LOADBALANCE_KEY, ""); v != "" {
lb = v
}
......@@ -68,7 +69,7 @@ func (invoker *failsafeClusterInvoker) Invoke(ctx context.Context, invocation pr
var result protocol.Result
ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked)
//DO INVOKE
// DO INVOKE
result = ivk.Invoke(ctx, invocation)
if result.Error() != nil {
// ignore
......
......@@ -40,10 +40,8 @@ import (
"github.com/apache/dubbo-go/protocol/mock"
)
var (
failsafeUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
)
var failsafeUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
// registerFailsafe register failsafeCluster to cluster extension.
func registerFailsafe(invoker *mock.MockInvoker) protocol.Invoker {
......
......@@ -42,10 +42,8 @@ import (
"github.com/apache/dubbo-go/protocol/mock"
)
var (
forkingUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
)
var forkingUrl, _ = common.NewURL(
fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT))
func registerForking(mockInvokers ...*mock.MockInvoker) protocol.Invoker {
extension.SetLoadbalance(loadbalance.RoundRobin, loadbalance.NewRoundRobinLoadBalance)
......@@ -72,7 +70,7 @@ func TestForkingInvokeSuccess(t *testing.T) {
mockResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}}
forkingUrl.AddParam(constant.FORKS_KEY, strconv.Itoa(3))
//forkingUrl.AddParam(constant.TIMEOUT_KEY, strconv.Itoa(constant.DEFAULT_TIMEOUT))
// forkingUrl.AddParam(constant.TIMEOUT_KEY, strconv.Itoa(constant.DEFAULT_TIMEOUT))
var wg sync.WaitGroup
wg.Add(2)
......
......@@ -125,7 +125,6 @@ func (invoker *zoneAwareClusterInvoker) BeforeInvoker(ctx context.Context, invoc
}
func (invoker *zoneAwareClusterInvoker) AfterInvoker(ctx context.Context, invocation protocol.Invocation) {
}
func matchParam(target, key, def string, invoker protocol.Invoker) bool {
......
......@@ -41,11 +41,12 @@ func TestZoneWareInvokerWithPreferredSuccess(t *testing.T) {
ctrl := gomock.NewController(t)
// In Go versions 1.14+, if you pass a *testing.T
// into gomock.NewController(t) you no longer need to call ctrl.Finish().
//defer ctrl.Finish()
// defer ctrl.Finish()
mockResult := &protocol.RPCResult{
Attrs: map[string]interface{}{constant.PREFERRED_KEY: "true"},
Rest: rest{tried: 0, success: true}}
Rest: rest{tried: 0, success: true},
}
var invokers []protocol.Invoker
for i := 0; i < 2; i++ {
......@@ -82,7 +83,7 @@ func TestZoneWareInvokerWithWeightSuccess(t *testing.T) {
ctrl := gomock.NewController(t)
// In Go versions 1.14+, if you pass a *testing.T
// into gomock.NewController(t) you no longer need to call ctrl.Finish().
//defer ctrl.Finish()
// defer ctrl.Finish()
w1 := "50"
w2 := "200"
......@@ -100,7 +101,8 @@ func TestZoneWareInvokerWithWeightSuccess(t *testing.T) {
func(invocation protocol.Invocation) protocol.Result {
return &protocol.RPCResult{
Attrs: map[string]interface{}{constant.WEIGHT_KEY: w1},
Rest: rest{tried: 0, success: true}}
Rest: rest{tried: 0, success: true},
}
}).MaxTimes(100)
} else {
url.SetParam(constant.REGISTRY_KEY+"."+constant.WEIGHT_KEY, w2)
......@@ -108,7 +110,8 @@ func TestZoneWareInvokerWithWeightSuccess(t *testing.T) {
func(invocation protocol.Invocation) protocol.Result {
return &protocol.RPCResult{
Attrs: map[string]interface{}{constant.WEIGHT_KEY: w2},
Rest: rest{tried: 0, success: true}}
Rest: rest{tried: 0, success: true},
}
}).MaxTimes(100)
}
invokers = append(invokers, invoker)
......@@ -135,12 +138,12 @@ func TestZoneWareInvokerWithWeightSuccess(t *testing.T) {
}
func TestZoneWareInvokerWithZoneSuccess(t *testing.T) {
var zoneArray = []string{"hangzhou", "shanghai"}
zoneArray := []string{"hangzhou", "shanghai"}
ctrl := gomock.NewController(t)
// In Go versions 1.14+, if you pass a *testing.T
// into gomock.NewController(t) you no longer need to call ctrl.Finish().
//defer ctrl.Finish()
// defer ctrl.Finish()
var invokers []protocol.Invoker
for i := 0; i < 2; i++ {
......@@ -155,7 +158,8 @@ func TestZoneWareInvokerWithZoneSuccess(t *testing.T) {
func(invocation protocol.Invocation) protocol.Result {
return &protocol.RPCResult{
Attrs: map[string]interface{}{constant.ZONE_KEY: zoneValue},
Rest: rest{tried: 0, success: true}}
Rest: rest{tried: 0, success: true},
}
})
invokers = append(invokers, invoker)
}
......@@ -178,7 +182,7 @@ func TestZoneWareInvokerWithZoneForceFail(t *testing.T) {
ctrl := gomock.NewController(t)
// In Go versions 1.14+, if you pass a *testing.T
// into gomock.NewController(t) you no longer need to call ctrl.Finish().
//defer ctrl.Finish()
// defer ctrl.Finish()
var invokers []protocol.Invoker
for i := 0; i < 2; i++ {
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment