github.com/mmatczuk/gohan@v0.0.0-20170206152520-30e45d9bdb69/sync/etcd/etcd.go (about) 1 // Copyright (C) 2015 NTT Innovation Institute, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 // implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package etcd 17 18 import ( 19 "encoding/json" 20 "fmt" 21 "os" 22 "time" 23 24 "github.com/cloudwan/gohan/sync" 25 "github.com/coreos/go-etcd/etcd" 26 cmap "github.com/streamrail/concurrent-map" 27 "github.com/twinj/uuid" 28 ) 29 30 const masterTTL = 10 31 32 //Sync is struct for etcd based sync 33 type Sync struct { 34 locks cmap.ConcurrentMap 35 etcdClient *etcd.Client 36 processID string 37 } 38 39 //NewSync initialize new etcd sync 40 func NewSync(etcdServers []string) *Sync { 41 sync := &Sync{locks: cmap.New()} 42 sync.etcdClient = etcd.NewClient(etcdServers) 43 hostname, _ := os.Hostname() 44 sync.processID = hostname + uuid.NewV4().String() 45 return sync 46 } 47 48 //Update sync update sync 49 func (s *Sync) Update(key, jsonString string) error { 50 var err error 51 if jsonString == "" { 52 _, err = s.etcdClient.SetDir(key, 0) 53 } else { 54 _, err = s.etcdClient.Set(key, jsonString, 0) 55 } 56 if err != nil { 57 log.Error(fmt.Sprintf("failed to sync with backend %s", err)) 58 return err 59 } 60 return nil 61 } 62 63 //Delete sync update sync 64 func (s *Sync) Delete(key string) error { 65 s.etcdClient.Delete(key, false) 66 return nil 67 } 68 69 //Fetch data from sync 70 func (s *Sync) Fetch(key string) (*sync.Node, error) { 71 resp, err := s.etcdClient.Get(key, true, true) 72 if err != nil { 73 return nil, err 74 } 75 76 return s.recursiveFetch(resp.Node) 77 } 78 79 func (s *Sync) recursiveFetch(node *etcd.Node) (*sync.Node, error) { 80 children := make([]*sync.Node, 0, len(node.Nodes)) 81 for _, child := range node.Nodes { 82 var err error 83 childNodes, err := s.recursiveFetch(child) 84 if err != nil { 85 return nil, err 86 } 87 children = append(children, childNodes) 88 } 89 90 n := &sync.Node{ 91 Key: node.Key, 92 Revision: int64(node.ModifiedIndex), 93 } 94 if node.Dir { 95 n.Children = children 96 } else { 97 n.Value = node.Value 98 } 99 100 return n, nil 101 } 102 103 //HasLock checks current process owns lock or not 104 func (s *Sync) HasLock(path string) bool { 105 value, ok := s.locks.Get(path) 106 isLocked, _ := value.(bool) 107 return isLocked && ok 108 } 109 110 // Lock locks resources on sync 111 // This call blocks until you can get lock 112 func (s *Sync) Lock(path string, block bool) error { 113 for { 114 if s.HasLock(path) { 115 return nil 116 } 117 _, err := s.etcdClient.Create(path, s.processID, masterTTL) 118 if err != nil { 119 log.Notice("failed to lock path %s: %s", path, err) 120 s.locks.Set(path, false) 121 if !block { 122 return err 123 } 124 time.Sleep(masterTTL * time.Second) 125 continue 126 } 127 log.Info("Locked %s", path) 128 s.locks.Set(path, true) 129 //Refresh master token 130 go func() { 131 for s.HasLock(path) { 132 _, err := s.etcdClient.CompareAndSwap( 133 path, s.processID, masterTTL, s.processID, 0) 134 if err != nil { 135 log.Notice("failed to keepalive lock for %s %s", path, err) 136 s.locks.Set(path, false) 137 return 138 } 139 time.Sleep(masterTTL / 2 * time.Second) 140 } 141 }() 142 return nil 143 } 144 } 145 146 //Unlock path 147 func (s *Sync) Unlock(path string) error { 148 s.locks.Set(path, false) 149 s.etcdClient.CompareAndDelete(path, s.processID, 0) 150 log.Info("Unlocked path %s", path) 151 return nil 152 } 153 154 func eventsFromNode(action string, node *etcd.Node, responseChan chan *sync.Event) { 155 event := &sync.Event{ 156 Action: action, 157 Key: node.Key, 158 Revision: int64(node.ModifiedIndex), 159 } 160 json.Unmarshal([]byte(node.Value), &event.Data) 161 responseChan <- event 162 for _, subnode := range node.Nodes { 163 eventsFromNode(action, subnode, responseChan) 164 } 165 } 166 167 //Watch keep watch update under the path 168 func (s *Sync) Watch(path string, responseChan chan *sync.Event, stopChan chan bool, revision int64) error { 169 etcdResponseChan := make(chan *etcd.Response) 170 response, err := s.etcdClient.Get(path, true, true) 171 if err != nil { 172 if etcdError, ok := err.(*etcd.EtcdError); ok { 173 switch etcdError.ErrorCode { 174 case 100: 175 response, err = s.etcdClient.CreateDir(path, 0) 176 if err != nil { 177 log.Error(fmt.Sprintf("failed to create dir: %s", err)) 178 return err 179 } 180 default: 181 log.Error(fmt.Sprintf("etcd error[%d]: %s ", etcdError.ErrorCode, etcdError)) 182 return err 183 } 184 } else { 185 log.Error(fmt.Sprintf("watch error: %s", err)) 186 return err 187 } 188 } 189 var lastIndex uint64 190 if revision == sync.RevisionCurrent { 191 lastIndex = response.EtcdIndex + 1 192 } else { 193 lastIndex = uint64(revision) 194 } 195 eventsFromNode(response.Action, response.Node, responseChan) 196 go func() { 197 _, err = s.etcdClient.Watch(path, lastIndex, true, etcdResponseChan, stopChan) 198 if err != nil { 199 log.Error(fmt.Sprintf("watch error: %s", err)) 200 stopChan <- true 201 return 202 } 203 }() 204 205 for { 206 select { 207 case response, ok := <-etcdResponseChan: 208 if !ok { 209 return nil 210 } 211 if response != nil { 212 event := &sync.Event{ 213 Action: response.Action, 214 Key: response.Node.Key, 215 } 216 json.Unmarshal([]byte(response.Node.Value), &event.Data) 217 responseChan <- event 218 } 219 case stop := <-stopChan: 220 if stop == true { 221 return nil 222 } 223 } 224 } 225 } 226 227 func (s *Sync) Close() { 228 // nothing to do 229 }