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

littlecui pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-service-center.git


The following commit(s) were added to refs/heads/master by this push:
     new 175853b  [feat] add dlock service, implemented etcd's dlock mechanism 
(#1191)
175853b is described below

commit 175853bb8fdd460f44f79c67d81d75c8e1eee161
Author: robotljw <[email protected]>
AuthorDate: Wed Dec 29 17:42:42 2021 +0800

    [feat] add dlock service, implemented etcd's dlock mechanism (#1191)
---
 datasource/dlock/dlock.go          | 33 +++++++++++++
 datasource/dlock/dlock_test.go     | 90 ++++++++++++++++++++++++++++++++++++
 datasource/dlock/init.go           | 56 +++++++++++++++++++++++
 datasource/dlock/options.go        | 23 ++++++++++
 datasource/etcd/dlock.go           | 90 ++++++++++++++++++++++++++++++++++++
 datasource/manager.go              |  9 ++++
 go.mod                             |  2 +-
 go.sum                             |  9 ----
 server/service/dlock/dlock.go      | 43 +++++++++++++++++
 server/service/dlock/dlock_test.go | 94 ++++++++++++++++++++++++++++++++++++++
 10 files changed, 439 insertions(+), 10 deletions(-)

diff --git a/datasource/dlock/dlock.go b/datasource/dlock/dlock.go
new file mode 100644
index 0000000..a2d0ea4
--- /dev/null
+++ b/datasource/dlock/dlock.go
@@ -0,0 +1,33 @@
+/*
+ * 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 dlock provide distributed lock function
+package dlock
+
+import (
+       "errors"
+)
+
+var ErrDLockNotExists = errors.New("DLock do not exist")
+
+type DLock interface {
+       Lock(key string, ttl int64) error
+       TryLock(key string, ttl int64) error
+       Renew(key string) error
+       IsHoldLock(key string) bool
+       Unlock(key string) error
+}
diff --git a/datasource/dlock/dlock_test.go b/datasource/dlock/dlock_test.go
new file mode 100644
index 0000000..640020d
--- /dev/null
+++ b/datasource/dlock/dlock_test.go
@@ -0,0 +1,90 @@
+/*
+ * 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 dlock_test
+
+import (
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/assert"
+
+       "github.com/apache/servicecomb-service-center/datasource/dlock"
+       _ "github.com/apache/servicecomb-service-center/test"
+)
+
+func TestDLock(t *testing.T) {
+       t.Run("test lock", func(t *testing.T) {
+               t.Run("lock the global key for 5s should pass", func(t 
*testing.T) {
+                       err := dlock.Instance().Lock("global", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.Instance().IsHoldLock("global")
+                       assert.Equal(t, true, isHold)
+               })
+               t.Run("two locks fight for the same lock 5s, one lock should 
pass, another lock should fail", func(t *testing.T) {
+                       err := dlock.Instance().Lock("same-lock", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.Instance().IsHoldLock("same-lock")
+                       assert.Equal(t, true, isHold)
+                       err = dlock.Instance().TryLock("same-lock", 5)
+                       assert.NotNil(t, err)
+               })
+       })
+       t.Run("test try lock", func(t *testing.T) {
+               t.Run("try lock the try key for 5s should pass", func(t 
*testing.T) {
+                       err := dlock.Instance().TryLock("try-lock", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.Instance().IsHoldLock("try-lock")
+                       assert.Equal(t, true, isHold)
+                       err = dlock.Instance().TryLock("try-lock", 5)
+                       assert.NotNil(t, err)
+               })
+       })
+       t.Run("test renew", func(t *testing.T) {
+               t.Run("renew the renew key for 5s should pass", func(t 
*testing.T) {
+                       err := dlock.Instance().Lock("renew", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.Instance().IsHoldLock("renew")
+                       assert.Equal(t, true, isHold)
+                       time.Sleep(3 * time.Second)
+                       err = dlock.Instance().Renew("renew")
+                       time.Sleep(2 * time.Second)
+                       err = dlock.Instance().TryLock("renew", 5)
+                       assert.NotNil(t, err)
+               })
+       })
+       t.Run("test isHoldLock", func(t *testing.T) {
+               t.Run("already owns the lock should pass", func(t *testing.T) {
+                       err := dlock.Instance().Lock("hold-lock", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.Instance().IsHoldLock("hold-lock")
+                       assert.Equal(t, true, isHold)
+               })
+               t.Run("key does not exist should fail", func(t *testing.T) {
+                       isHold := dlock.Instance().IsHoldLock("not-exist")
+                       assert.Equal(t, false, isHold)
+               })
+       })
+       t.Run("test unlock", func(t *testing.T) {
+               t.Run("unlock the unlock key should pass", func(t *testing.T) {
+                       err := dlock.Instance().Lock("unlock", 5)
+                       assert.Nil(t, err)
+                       err = dlock.Instance().Unlock("unlock")
+                       assert.Nil(t, err)
+               })
+       })
+}
diff --git a/datasource/dlock/init.go b/datasource/dlock/init.go
new file mode 100644
index 0000000..179b255
--- /dev/null
+++ b/datasource/dlock/init.go
@@ -0,0 +1,56 @@
+/*
+ * 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 dlock
+
+import (
+       "fmt"
+
+       "github.com/apache/servicecomb-service-center/pkg/log"
+)
+
+type initFunc func(opts Options) (DLock, error)
+
+var (
+       plugins  = make(map[string]initFunc)
+       instance DLock
+)
+
+func Install(pluginImplName string, f initFunc) {
+       plugins[pluginImplName] = f
+}
+
+func Init(opts Options) error {
+       if opts.Kind == "" {
+               return nil
+       }
+       engineFunc, ok := plugins[opts.Kind]
+       if !ok {
+               return fmt.Errorf("plugin implement not supported [%s]", 
opts.Kind)
+       }
+       var err error
+       instance, err = engineFunc(opts)
+       if err != nil {
+               return err
+       }
+       log.Info(fmt.Sprintf("dlock plugin [%s] enabled", opts.Kind))
+       return nil
+}
+
+func Instance() DLock {
+       return instance
+}
diff --git a/datasource/dlock/options.go b/datasource/dlock/options.go
new file mode 100644
index 0000000..1dfe186
--- /dev/null
+++ b/datasource/dlock/options.go
@@ -0,0 +1,23 @@
+/*
+ * 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 dlock
+
+// Options contains configuration for plugins
+type Options struct {
+       Kind string
+}
diff --git a/datasource/etcd/dlock.go b/datasource/etcd/dlock.go
new file mode 100644
index 0000000..13dcf29
--- /dev/null
+++ b/datasource/etcd/dlock.go
@@ -0,0 +1,90 @@
+/*
+ * 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 etcd
+
+import (
+       "sync"
+
+       "github.com/go-chassis/openlog"
+       "github.com/little-cui/etcdadpt"
+
+       "github.com/apache/servicecomb-service-center/datasource/dlock"
+)
+
+func init() {
+       dlock.Install("etcd", NewDLock)
+       dlock.Install("embeded_etcd", NewDLock)
+       dlock.Install("embedded_etcd", NewDLock)
+}
+
+func NewDLock(opts dlock.Options) (dlock.DLock, error) {
+       return &DB{lockMap: sync.Map{}}, nil
+}
+
+type DB struct {
+       lockMap sync.Map
+}
+
+func (d *DB) Lock(key string, ttl int64) error {
+       lock, err := etcdadpt.Lock(key, ttl)
+       if err == nil {
+               d.lockMap.Store(key, lock)
+       }
+       return err
+}
+
+func (d *DB) TryLock(key string, ttl int64) error {
+       lock, err := etcdadpt.TryLock(key, ttl)
+       if err == nil {
+               d.lockMap.Store(key, lock)
+       }
+       return err
+}
+
+func (d *DB) Renew(key string) error {
+       if lock, ok := d.lockMap.Load(key); ok {
+               err := lock.(*etcdadpt.DLock).Refresh()
+               if err != nil {
+                       openlog.Error("fail to renew key")
+                       d.lockMap.Delete(key)
+               }
+               return err
+       }
+       return dlock.ErrDLockNotExists
+}
+
+func (d *DB) IsHoldLock(key string) bool {
+       if lock, ok := d.lockMap.Load(key); ok {
+               if lock != nil {
+                       return true
+               }
+       }
+       return false
+}
+
+func (d *DB) Unlock(key string) error {
+       if lock, ok := d.lockMap.Load(key); ok {
+               err := lock.(*etcdadpt.DLock).Unlock()
+               if err != nil {
+                       openlog.Error("fail to unlock")
+               }
+               d.lockMap.Delete(key)
+               return err
+       }
+       return dlock.ErrDLockNotExists
+}
diff --git a/datasource/manager.go b/datasource/manager.go
index 5cb56dc..0c78c71 100644
--- a/datasource/manager.go
+++ b/datasource/manager.go
@@ -20,6 +20,7 @@ package datasource
 import (
        "fmt"
 
+       "github.com/apache/servicecomb-service-center/datasource/dlock"
        "github.com/apache/servicecomb-service-center/datasource/rbac"
        "github.com/apache/servicecomb-service-center/datasource/schema"
        "github.com/apache/servicecomb-service-center/pkg/log"
@@ -56,6 +57,14 @@ func Init(opts Options) error {
        if err != nil {
                return err
        }
+
+       err = dlock.Init(dlock.Options{
+               Kind: opts.Kind,
+       })
+       if err != nil {
+               return err
+       }
+
        return nil
 }
 
diff --git a/go.mod b/go.mod
index 63b01a4..8825492 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,6 @@ replace (
 )
 
 require (
-       github.com/robfig/cron/v3 v3.0.1
        github.com/NYTimes/gziphandler v1.1.1
        github.com/apache/servicecomb-service-center/api v0.0.0
        github.com/astaxie/beego v1.12.2
@@ -38,6 +37,7 @@ require (
        github.com/prometheus/client_golang v1.11.0
        github.com/prometheus/client_model v0.2.0
        github.com/prometheus/procfs v0.6.0
+       github.com/robfig/cron/v3 v3.0.1
        github.com/rs/cors v1.7.0 // v1.1
        github.com/satori/go.uuid v1.1.0
        github.com/spf13/cobra v1.1.3
diff --git a/go.sum b/go.sum
index 7f895cf..e1f4c40 100644
--- a/go.sum
+++ b/go.sum
@@ -184,9 +184,6 @@ github.com/go-chassis/cari 
v0.5.1-0.20211208092532-78a52aa9d52e/go.mod h1:av/19f
 github.com/go-chassis/foundation v0.2.2-0.20201210043510-9f6d3de40234/go.mod 
h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
 github.com/go-chassis/foundation v0.2.2/go.mod 
h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
 github.com/go-chassis/foundation v0.3.0/go.mod 
h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA=
-github.com/go-chassis/foundation v0.3.1-0.20210806081520-3bd92d1ef787/go.mod 
h1:6NsIUaHghTFRGfCBcZN011zl196F6OR5QvD9N+P4oWU=
-github.com/go-chassis/foundation v0.3.1-0.20210811025651-7f4d2b2b906c 
h1:6ooUyysnayGgoJHV++NLEcnnnXzsw3ud4VydjISGBqI=
-github.com/go-chassis/foundation v0.3.1-0.20210811025651-7f4d2b2b906c/go.mod 
h1:6NsIUaHghTFRGfCBcZN011zl196F6OR5QvD9N+P4oWU=
 github.com/go-chassis/foundation v0.4.0 
h1:z0xETnSxF+vRXWjoIhOdzt6rywjZ4sB++utEl4YgWEY=
 github.com/go-chassis/foundation v0.4.0/go.mod 
h1:6NsIUaHghTFRGfCBcZN011zl196F6OR5QvD9N+P4oWU=
 github.com/go-chassis/go-archaius v1.5.1 
h1:1FrNyzzmD6o6BIjPF8uQ4Cc+u7qYIgQTpDk8uopBqfo=
@@ -436,12 +433,6 @@ github.com/labstack/gommon v0.3.0/go.mod 
h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
 github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod 
h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
 github.com/leodido/go-urn v1.2.1/go.mod 
h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/little-cui/etcdadpt v0.2.1 
h1:eT1A+BV1/2/dmmZA2Nl+cc7uTMuwd6T6DD+JrXr8xcA=
-github.com/little-cui/etcdadpt v0.2.1/go.mod 
h1:727wftF2FS4vfkgFLmIvQue1XH+9u4lK2/hd6L7OAC8=
-github.com/little-cui/etcdadpt v0.2.2-0.20211218040008-804e734f410b 
h1:GeNmlkSJKu1B8pNM18g+u8O/7BJNd9TP6KrfePy7Z6w=
-github.com/little-cui/etcdadpt v0.2.2-0.20211218040008-804e734f410b/go.mod 
h1:HnRRpIrVEVNWobkiCvG2EHLWKKZ+L047EcI29ma2zA4=
-github.com/little-cui/etcdadpt v0.2.2-0.20211222115540-fc5b1296d8b5 
h1:SlD/2mPGjzkg2oj4ndoR9u/yadPcyE8om3njQEI65bE=
-github.com/little-cui/etcdadpt v0.2.2-0.20211222115540-fc5b1296d8b5/go.mod 
h1:HnRRpIrVEVNWobkiCvG2EHLWKKZ+L047EcI29ma2zA4=
 github.com/little-cui/etcdadpt v0.3.1 
h1:lAPIffcOR6jROu/mWf+zHscV8urIu1qbsJvwvziLWDY=
 github.com/little-cui/etcdadpt v0.3.1/go.mod 
h1:HnRRpIrVEVNWobkiCvG2EHLWKKZ+L047EcI29ma2zA4=
 github.com/magiconair/properties v1.8.1/go.mod 
h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
diff --git a/server/service/dlock/dlock.go b/server/service/dlock/dlock.go
new file mode 100644
index 0000000..ac7c914
--- /dev/null
+++ b/server/service/dlock/dlock.go
@@ -0,0 +1,43 @@
+/*
+ * 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 dlock provide distributed lock function
+package dlock
+
+import (
+       "github.com/apache/servicecomb-service-center/datasource/dlock"
+)
+
+func Lock(key string, ttl int64) error {
+       return dlock.Instance().Lock(key, ttl)
+}
+
+func TryLock(key string, ttl int64) error {
+       return dlock.Instance().TryLock(key, ttl)
+}
+
+func Renew(key string) error {
+       return dlock.Instance().Renew(key)
+}
+
+func IsHoldLock(key string) bool {
+       return dlock.Instance().IsHoldLock(key)
+}
+
+func Unlock(key string) error {
+       return dlock.Instance().Unlock(key)
+}
diff --git a/server/service/dlock/dlock_test.go 
b/server/service/dlock/dlock_test.go
new file mode 100644
index 0000000..625bb0c
--- /dev/null
+++ b/server/service/dlock/dlock_test.go
@@ -0,0 +1,94 @@
+/*
+ * 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 dlock_test
+
+import (
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/assert"
+
+       "github.com/apache/servicecomb-service-center/server/service/dlock"
+       "github.com/apache/servicecomb-service-center/test"
+       _ "github.com/apache/servicecomb-service-center/test"
+)
+
+func TestDLock(t *testing.T) {
+       if !test.IsETCD() {
+               return
+       }
+       t.Run("test lock", func(t *testing.T) {
+               t.Run("lock the global key for 5s should pass", func(t 
*testing.T) {
+                       err := dlock.Lock("global", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.IsHoldLock("global")
+                       assert.Equal(t, true, isHold)
+               })
+               t.Run("two locks fight for the same lock 5s, one lock should 
pass, another lock should fail", func(t *testing.T) {
+                       err := dlock.Lock("same-lock", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.IsHoldLock("same-lock")
+                       assert.Equal(t, true, isHold)
+                       err = dlock.TryLock("same-lock", 5)
+                       assert.NotNil(t, err)
+               })
+       })
+       t.Run("test try lock", func(t *testing.T) {
+               t.Run("try lock the try key for 5s should pass", func(t 
*testing.T) {
+                       err := dlock.TryLock("try-lock", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.IsHoldLock("try-lock")
+                       assert.Equal(t, true, isHold)
+                       err = dlock.TryLock("try-lock", 5)
+                       assert.NotNil(t, err)
+               })
+       })
+       t.Run("test renew", func(t *testing.T) {
+               t.Run("renew the renew key for 5s should pass", func(t 
*testing.T) {
+                       err := dlock.Lock("renew", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.IsHoldLock("renew")
+                       assert.Equal(t, true, isHold)
+                       time.Sleep(3 * time.Second)
+                       err = dlock.Renew("renew")
+                       time.Sleep(2 * time.Second)
+                       err = dlock.TryLock("renew", 5)
+                       assert.NotNil(t, err)
+               })
+       })
+       t.Run("test isHoldLock", func(t *testing.T) {
+               t.Run("already owns the lock should pass", func(t *testing.T) {
+                       err := dlock.Lock("hold-lock", 5)
+                       assert.Nil(t, err)
+                       isHold := dlock.IsHoldLock("hold-lock")
+                       assert.Equal(t, true, isHold)
+               })
+               t.Run("key does not exist should fail", func(t *testing.T) {
+                       isHold := dlock.IsHoldLock("not-exist")
+                       assert.Equal(t, false, isHold)
+               })
+       })
+       t.Run("test unlock", func(t *testing.T) {
+               t.Run("unlock the unlock key should pass", func(t *testing.T) {
+                       err := dlock.Lock("unlock", 5)
+                       assert.Nil(t, err)
+                       err = dlock.Unlock("unlock")
+                       assert.Nil(t, err)
+               })
+       })
+}

Reply via email to