github.com/jbking/gohan@v0.0.0-20151217002006-b41ccf1c2a96/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 _, err := s.etcdClient.Set(key, jsonString, 0) 51 if err != nil { 52 log.Error(fmt.Sprintf("failed to sync with backend %s", err)) 53 return err 54 } 55 return nil 56 } 57 58 //Delete sync update sync 59 func (s *Sync) Delete(key string) error { 60 s.etcdClient.Delete(key, false) 61 return nil 62 } 63 64 //Fetch data from sync 65 func (s *Sync) Fetch(key string) (interface{}, error) { 66 return s.etcdClient.Get(key, true, true) 67 } 68 69 //HasLock checks current process owns lock or not 70 func (s *Sync) HasLock(path string) bool { 71 value, ok := s.locks.Get(path) 72 isLocked, _ := value.(bool) 73 return isLocked && ok 74 } 75 76 // Lock locks resources on sync 77 // This call blocks until you can get lock 78 func (s *Sync) Lock(path string, block bool) error { 79 for { 80 if s.HasLock(path) { 81 return nil 82 } 83 _, err := s.etcdClient.Create(path, s.processID, masterTTL) 84 if err != nil { 85 log.Notice("failed to lock path %s: %s", path, err) 86 s.locks.Set(path, false) 87 if !block { 88 return err 89 } 90 time.Sleep(masterTTL * time.Second) 91 continue 92 } 93 log.Info("Locked %s", path) 94 s.locks.Set(path, true) 95 //Refresh master token 96 go func() { 97 for s.HasLock(path) { 98 _, err := s.etcdClient.CompareAndSwap( 99 path, s.processID, masterTTL, s.processID, 0) 100 if err != nil { 101 log.Notice("failed to keepalive lock for %s %s", path, err) 102 s.locks.Set(path, false) 103 return 104 } 105 time.Sleep(masterTTL / 2 * time.Second) 106 } 107 }() 108 return nil 109 } 110 } 111 112 //Unlock path 113 func (s *Sync) Unlock(path string) error { 114 s.locks.Set(path, false) 115 s.etcdClient.CompareAndDelete(path, s.processID, 0) 116 log.Info("Unlocked path %s", path) 117 return nil 118 } 119 120 func eventsFromNode(action string, node *etcd.Node, responseChan chan *sync.Event) { 121 event := &sync.Event{ 122 Action: action, 123 Key: node.Key, 124 } 125 json.Unmarshal([]byte(node.Value), &event.Data) 126 responseChan <- event 127 for _, subnode := range node.Nodes { 128 eventsFromNode(action, subnode, responseChan) 129 } 130 } 131 132 //Watch keep watch update under the path 133 func (s *Sync) Watch(path string, responseChan chan *sync.Event, stopChan chan bool) error { 134 var etcdResponseChan chan *etcd.Response 135 136 etcdResponseChan = make(chan *etcd.Response) 137 response, err := s.etcdClient.Get(path, true, true) 138 if err != nil { 139 if etcdError, ok := err.(*etcd.EtcdError); ok { 140 switch etcdError.ErrorCode { 141 case 100: 142 response, err = s.etcdClient.CreateDir(path, 0) 143 if err != nil { 144 log.Error(fmt.Sprintf("failed to create dir: %s", err)) 145 return err 146 } 147 default: 148 log.Error(fmt.Sprintf("etcd error[%d]: %s ", etcdError.ErrorCode, etcdError)) 149 return err 150 } 151 } else { 152 log.Error(fmt.Sprintf("watch error: %s", err)) 153 return err 154 } 155 } 156 lastIndex := response.EtcdIndex + 1 157 eventsFromNode(response.Action, response.Node, responseChan) 158 go func() { 159 _, err = s.etcdClient.Watch(path, lastIndex, true, etcdResponseChan, stopChan) 160 if err != nil { 161 log.Error(fmt.Sprintf("watch error: %s", err)) 162 stopChan <- true 163 return 164 } 165 }() 166 167 for { 168 select { 169 case response := <-etcdResponseChan: 170 if response != nil { 171 event := &sync.Event{ 172 Action: response.Action, 173 Key: response.Node.Key, 174 } 175 json.Unmarshal([]byte(response.Node.Value), &event.Data) 176 responseChan <- event 177 } 178 case stop := <-stopChan: 179 if stop == true { 180 return nil 181 } 182 } 183 } 184 }