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  }