mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-16 14:56:16 +01:00
473 lines
9.2 KiB
Go
473 lines
9.2 KiB
Go
|
package zkhelper
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"path"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
etcderr "github.com/coreos/etcd/error"
|
||
|
"github.com/coreos/go-etcd/etcd"
|
||
|
zk "github.com/ngaut/go-zookeeper/zk"
|
||
|
"github.com/ngaut/log"
|
||
|
"github.com/ngaut/pools"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
singleInstanceLock sync.Mutex
|
||
|
etcdInstance *etcdImpl
|
||
|
)
|
||
|
|
||
|
type PooledEtcdClient struct {
|
||
|
c *etcd.Client
|
||
|
}
|
||
|
|
||
|
func (c *PooledEtcdClient) Close() {
|
||
|
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) Seq2Str(seq int64) string {
|
||
|
return fmt.Sprintf("%d", seq)
|
||
|
}
|
||
|
|
||
|
type etcdImpl struct {
|
||
|
sync.Mutex
|
||
|
cluster string
|
||
|
pool *pools.ResourcePool
|
||
|
indexMap map[string]uint64
|
||
|
}
|
||
|
|
||
|
func convertToZkError(err error) error {
|
||
|
//todo: convert other errors
|
||
|
if ec, ok := err.(*etcd.EtcdError); ok {
|
||
|
switch ec.ErrorCode {
|
||
|
case etcderr.EcodeKeyNotFound:
|
||
|
return zk.ErrNoNode
|
||
|
case etcderr.EcodeNotFile:
|
||
|
case etcderr.EcodeNotDir:
|
||
|
case etcderr.EcodeNodeExist:
|
||
|
return zk.ErrNodeExists
|
||
|
case etcderr.EcodeDirNotEmpty:
|
||
|
return zk.ErrNotEmpty
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func convertToZkEvent(watchPath string, resp *etcd.Response, err error) zk.Event {
|
||
|
//log.Infof("convert event from path:%s, %+v, %+v", watchPath, resp, resp.Node.Key)
|
||
|
var e zk.Event
|
||
|
|
||
|
if err != nil {
|
||
|
e.Err = convertToZkError(err)
|
||
|
e.State = zk.StateDisconnected
|
||
|
return e
|
||
|
}
|
||
|
|
||
|
e.State = zk.StateConnected
|
||
|
|
||
|
e.Path = resp.Node.Key
|
||
|
if len(resp.Node.Key) > len(watchPath) {
|
||
|
e.Type = zk.EventNodeChildrenChanged
|
||
|
return e
|
||
|
}
|
||
|
|
||
|
switch resp.Action {
|
||
|
case "set":
|
||
|
e.Type = zk.EventNodeDataChanged
|
||
|
case "delete":
|
||
|
e.Type = zk.EventNodeDeleted
|
||
|
case "update":
|
||
|
e.Type = zk.EventNodeDataChanged
|
||
|
case "create":
|
||
|
e.Type = zk.EventNodeCreated
|
||
|
case "expire":
|
||
|
e.Type = zk.EventNotWatching
|
||
|
}
|
||
|
|
||
|
return e
|
||
|
}
|
||
|
|
||
|
func NewEtcdConn(zkAddr string) (Conn, error) {
|
||
|
singleInstanceLock.Lock()
|
||
|
defer singleInstanceLock.Unlock()
|
||
|
if etcdInstance != nil {
|
||
|
return etcdInstance, nil
|
||
|
}
|
||
|
|
||
|
p := pools.NewResourcePool(func() (pools.Resource, error) {
|
||
|
cluster := strings.Split(zkAddr, ",")
|
||
|
for i, addr := range cluster {
|
||
|
if !strings.HasPrefix(addr, "http://") {
|
||
|
cluster[i] = "http://" + addr
|
||
|
}
|
||
|
}
|
||
|
newClient := etcd.NewClient(cluster)
|
||
|
newClient.SetConsistency(etcd.STRONG_CONSISTENCY)
|
||
|
return &PooledEtcdClient{c: newClient}, nil
|
||
|
}, 10, 10, 0)
|
||
|
|
||
|
etcdInstance = &etcdImpl{
|
||
|
cluster: zkAddr,
|
||
|
pool: p,
|
||
|
indexMap: make(map[string]uint64),
|
||
|
}
|
||
|
|
||
|
log.Infof("new etcd %s", zkAddr)
|
||
|
if etcdInstance == nil {
|
||
|
return nil, errors.New("unknown error")
|
||
|
}
|
||
|
|
||
|
return etcdInstance, nil
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) Get(key string) (data []byte, stat zk.Stat, err error) {
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
defer e.pool.Put(conn)
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
|
||
|
resp, err := c.Get(key, true, false)
|
||
|
if resp == nil {
|
||
|
return nil, nil, convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
return []byte(resp.Node.Value), nil, nil
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) setIndex(key string, index uint64) {
|
||
|
e.Lock()
|
||
|
defer e.Unlock()
|
||
|
|
||
|
e.indexMap[key] = index
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) getIndex(key string) uint64 {
|
||
|
e.Lock()
|
||
|
defer e.Unlock()
|
||
|
|
||
|
index := e.indexMap[key]
|
||
|
|
||
|
return index
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) watch(key string, children bool) (resp *etcd.Response, stat zk.Stat, watch <-chan zk.Event, err error) {
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
return nil, nil, nil, err
|
||
|
}
|
||
|
|
||
|
defer e.pool.Put(conn)
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
index := e.getIndex(key)
|
||
|
resp, err = c.Get(key, true, true)
|
||
|
if resp == nil {
|
||
|
return nil, nil, nil, convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
if index < resp.Node.ModifiedIndex {
|
||
|
index = resp.Node.ModifiedIndex
|
||
|
}
|
||
|
|
||
|
for _, n := range resp.Node.Nodes {
|
||
|
if n.ModifiedIndex > index {
|
||
|
index = n.ModifiedIndex
|
||
|
}
|
||
|
}
|
||
|
|
||
|
log.Info("try watch", key)
|
||
|
ch := make(chan zk.Event, 100)
|
||
|
originVal := resp.Node.Value
|
||
|
|
||
|
go func() {
|
||
|
defer func() {
|
||
|
e.setIndex(key, index)
|
||
|
}()
|
||
|
|
||
|
for {
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
log.Error(err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
|
||
|
resp, err := c.Watch(key, index, children, nil, nil)
|
||
|
e.pool.Put(conn)
|
||
|
|
||
|
if err != nil {
|
||
|
if ec, ok := err.(*etcd.EtcdError); ok {
|
||
|
if ec.ErrorCode == etcderr.EcodeEventIndexCleared {
|
||
|
index++
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
log.Warning("watch", err)
|
||
|
ch <- convertToZkEvent(key, resp, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if key == resp.Node.Key && originVal == string(resp.Node.Value) { //keep alive event
|
||
|
index++
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
ch <- convertToZkEvent(key, resp, err)
|
||
|
//update index
|
||
|
if index <= resp.Node.ModifiedIndex {
|
||
|
index = resp.Node.ModifiedIndex + 1
|
||
|
} else {
|
||
|
index++
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
return resp, nil, ch, nil
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) GetW(key string) (data []byte, stat zk.Stat, watch <-chan zk.Event, err error) {
|
||
|
resp, stat, watch, err := e.watch(key, false)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
return []byte(resp.Node.Value), stat, watch, nil
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) Children(key string) (children []string, stat zk.Stat, err error) {
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
defer e.pool.Put(conn)
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
|
||
|
resp, err := c.Get(key, true, false)
|
||
|
if resp == nil {
|
||
|
return nil, nil, convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
for _, c := range resp.Node.Nodes {
|
||
|
children = append(children, path.Base(c.Key))
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) ChildrenW(key string) (children []string, stat zk.Stat, watch <-chan zk.Event, err error) {
|
||
|
resp, stat, watch, err := e.watch(key, true)
|
||
|
if err != nil {
|
||
|
return nil, stat, nil, convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
for _, c := range resp.Node.Nodes {
|
||
|
children = append(children, path.Base(c.Key))
|
||
|
}
|
||
|
|
||
|
return children, stat, watch, nil
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) Exists(key string) (exist bool, stat zk.Stat, err error) {
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
return false, nil, err
|
||
|
}
|
||
|
|
||
|
defer e.pool.Put(conn)
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
|
||
|
_, err = c.Get(key, true, false)
|
||
|
if err == nil {
|
||
|
return true, nil, nil
|
||
|
}
|
||
|
|
||
|
if ec, ok := err.(*etcd.EtcdError); ok {
|
||
|
if ec.ErrorCode == etcderr.EcodeKeyNotFound {
|
||
|
return false, nil, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false, nil, convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) ExistsW(key string) (exist bool, stat zk.Stat, watch <-chan zk.Event, err error) {
|
||
|
_, stat, watch, err = e.watch(key, false)
|
||
|
if err != nil {
|
||
|
return false, nil, nil, convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
return true, nil, watch, nil
|
||
|
}
|
||
|
|
||
|
const MAX_TTL = 365 * 24 * 60 * 60
|
||
|
|
||
|
func (e *etcdImpl) doKeepAlive(key string, ttl uint64) error {
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
defer e.pool.Put(conn)
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
|
||
|
resp, err := c.Get(key, false, false)
|
||
|
if err != nil {
|
||
|
log.Error(err)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if resp.Node.Dir {
|
||
|
return fmt.Errorf("can not set ttl to directory", key)
|
||
|
}
|
||
|
|
||
|
//log.Info("keep alive ", key)
|
||
|
resp, err = c.CompareAndSwap(key, resp.Node.Value, ttl, resp.Node.Value, resp.Node.ModifiedIndex)
|
||
|
if err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if ec, ok := err.(*etcd.EtcdError); ok && ec.ErrorCode == etcderr.EcodeTestFailed {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
//todo:add test for keepAlive
|
||
|
func (e *etcdImpl) keepAlive(key string, ttl uint64) {
|
||
|
go func() {
|
||
|
for {
|
||
|
time.Sleep(1 * time.Second)
|
||
|
err := e.doKeepAlive(key, ttl)
|
||
|
if err != nil {
|
||
|
log.Error(err)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) Create(wholekey string, value []byte, flags int32, aclv []zk.ACL) (keyCreated string, err error) {
|
||
|
seq := (flags & zk.FlagSequence) != 0
|
||
|
tmp := (flags & zk.FlagEphemeral) != 0
|
||
|
ttl := uint64(MAX_TTL)
|
||
|
if tmp {
|
||
|
ttl = 5
|
||
|
}
|
||
|
|
||
|
var resp *etcd.Response
|
||
|
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
defer e.pool.Put(conn)
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
|
||
|
fn := c.Create
|
||
|
log.Info("create", wholekey)
|
||
|
|
||
|
if seq {
|
||
|
wholekey = path.Dir(wholekey)
|
||
|
fn = c.CreateInOrder
|
||
|
} else {
|
||
|
for _, v := range aclv {
|
||
|
if v.Perms == PERM_DIRECTORY {
|
||
|
log.Info("etcdImpl:create directory", wholekey)
|
||
|
fn = nil
|
||
|
resp, err = c.CreateDir(wholekey, uint64(ttl))
|
||
|
if err != nil {
|
||
|
return "", convertToZkError(err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if fn == nil {
|
||
|
if tmp {
|
||
|
e.keepAlive(wholekey, ttl)
|
||
|
}
|
||
|
return resp.Node.Key, nil
|
||
|
}
|
||
|
|
||
|
resp, err = fn(wholekey, string(value), uint64(ttl))
|
||
|
if err != nil {
|
||
|
return "", convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
if tmp {
|
||
|
e.keepAlive(resp.Node.Key, ttl)
|
||
|
}
|
||
|
|
||
|
return resp.Node.Key, nil
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) Set(key string, value []byte, version int32) (stat zk.Stat, err error) {
|
||
|
if version == 0 {
|
||
|
return nil, errors.New("invalid version")
|
||
|
}
|
||
|
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
defer e.pool.Put(conn)
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
|
||
|
resp, err := c.Get(key, true, false)
|
||
|
if resp == nil {
|
||
|
return nil, convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
_, err = c.Set(key, string(value), uint64(resp.Node.TTL))
|
||
|
return nil, convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) Delete(key string, version int32) (err error) {
|
||
|
//todo: handle version
|
||
|
conn, err := e.pool.Get()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
defer e.pool.Put(conn)
|
||
|
c := conn.(*PooledEtcdClient).c
|
||
|
|
||
|
resp, err := c.Get(key, true, false)
|
||
|
if resp == nil {
|
||
|
return convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
if resp.Node.Dir {
|
||
|
_, err = c.DeleteDir(key)
|
||
|
} else {
|
||
|
_, err = c.Delete(key, false)
|
||
|
}
|
||
|
|
||
|
return convertToZkError(err)
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) GetACL(key string) ([]zk.ACL, zk.Stat, error) {
|
||
|
return nil, nil, nil
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) SetACL(key string, aclv []zk.ACL, version int32) (zk.Stat, error) {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
func (e *etcdImpl) Close() {
|
||
|
//how to implement this
|
||
|
}
|