You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
5.5 KiB

5 years ago
5 years ago
5 years ago
  1. package etcd
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "time"
  7. "go.etcd.io/etcd/clientv3"
  8. "github.com/chrislusf/seaweedfs/weed/filer"
  9. "github.com/chrislusf/seaweedfs/weed/glog"
  10. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  11. weed_util "github.com/chrislusf/seaweedfs/weed/util"
  12. )
  13. const (
  14. DIR_FILE_SEPARATOR = byte(0x00)
  15. )
  16. func init() {
  17. filer.Stores = append(filer.Stores, &EtcdStore{})
  18. }
  19. type EtcdStore struct {
  20. client *clientv3.Client
  21. }
  22. func (store *EtcdStore) GetName() string {
  23. return "etcd"
  24. }
  25. func (store *EtcdStore) Initialize(configuration weed_util.Configuration, prefix string) (err error) {
  26. servers := configuration.GetString(prefix + "servers")
  27. if servers == "" {
  28. servers = "localhost:2379"
  29. }
  30. timeout := configuration.GetString(prefix + "timeout")
  31. if timeout == "" {
  32. timeout = "3s"
  33. }
  34. return store.initialize(servers, timeout)
  35. }
  36. func (store *EtcdStore) initialize(servers string, timeout string) (err error) {
  37. glog.Infof("filer store etcd: %s", servers)
  38. to, err := time.ParseDuration(timeout)
  39. if err != nil {
  40. return fmt.Errorf("parse timeout %s: %s", timeout, err)
  41. }
  42. store.client, err = clientv3.New(clientv3.Config{
  43. Endpoints: strings.Split(servers, ","),
  44. DialTimeout: to,
  45. })
  46. if err != nil {
  47. return fmt.Errorf("connect to etcd %s: %s", servers, err)
  48. }
  49. return
  50. }
  51. func (store *EtcdStore) BeginTransaction(ctx context.Context) (context.Context, error) {
  52. return ctx, nil
  53. }
  54. func (store *EtcdStore) CommitTransaction(ctx context.Context) error {
  55. return nil
  56. }
  57. func (store *EtcdStore) RollbackTransaction(ctx context.Context) error {
  58. return nil
  59. }
  60. func (store *EtcdStore) InsertEntry(ctx context.Context, entry *filer.Entry) (err error) {
  61. key := genKey(entry.DirAndName())
  62. meta, err := entry.EncodeAttributesAndChunks()
  63. if err != nil {
  64. return fmt.Errorf("encoding %s %+v: %v", entry.FullPath, entry.Attr, err)
  65. }
  66. if len(entry.Chunks) > 50 {
  67. meta = weed_util.MaybeGzipData(meta)
  68. }
  69. if _, err := store.client.Put(ctx, string(key), string(meta)); err != nil {
  70. return fmt.Errorf("persisting %s : %v", entry.FullPath, err)
  71. }
  72. return nil
  73. }
  74. func (store *EtcdStore) UpdateEntry(ctx context.Context, entry *filer.Entry) (err error) {
  75. return store.InsertEntry(ctx, entry)
  76. }
  77. func (store *EtcdStore) FindEntry(ctx context.Context, fullpath weed_util.FullPath) (entry *filer.Entry, err error) {
  78. key := genKey(fullpath.DirAndName())
  79. resp, err := store.client.Get(ctx, string(key))
  80. if err != nil {
  81. return nil, fmt.Errorf("get %s : %v", fullpath, err)
  82. }
  83. if len(resp.Kvs) == 0 {
  84. return nil, filer_pb.ErrNotFound
  85. }
  86. entry = &filer.Entry{
  87. FullPath: fullpath,
  88. }
  89. err = entry.DecodeAttributesAndChunks(weed_util.MaybeDecompressData(resp.Kvs[0].Value))
  90. if err != nil {
  91. return entry, fmt.Errorf("decode %s : %v", entry.FullPath, err)
  92. }
  93. return entry, nil
  94. }
  95. func (store *EtcdStore) DeleteEntry(ctx context.Context, fullpath weed_util.FullPath) (err error) {
  96. key := genKey(fullpath.DirAndName())
  97. if _, err := store.client.Delete(ctx, string(key)); err != nil {
  98. return fmt.Errorf("delete %s : %v", fullpath, err)
  99. }
  100. return nil
  101. }
  102. func (store *EtcdStore) DeleteFolderChildren(ctx context.Context, fullpath weed_util.FullPath) (err error) {
  103. directoryPrefix := genDirectoryKeyPrefix(fullpath, "")
  104. if _, err := store.client.Delete(ctx, string(directoryPrefix), clientv3.WithPrefix()); err != nil {
  105. return fmt.Errorf("deleteFolderChildren %s : %v", fullpath, err)
  106. }
  107. return nil
  108. }
  109. func (store *EtcdStore) ListDirectoryPrefixedEntries(ctx context.Context, dirPath weed_util.FullPath, startFileName string, includeStartFile bool, limit int64, prefix string, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
  110. return lastFileName, filer.ErrUnsupportedListDirectoryPrefixed
  111. }
  112. func (store *EtcdStore) ListDirectoryEntries(ctx context.Context, dirPath weed_util.FullPath, startFileName string, includeStartFile bool, limit int64, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
  113. directoryPrefix := genDirectoryKeyPrefix(dirPath, "")
  114. resp, err := store.client.Get(ctx, string(directoryPrefix),
  115. clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortDescend))
  116. if err != nil {
  117. return lastFileName, fmt.Errorf("list %s : %v", dirPath, err)
  118. }
  119. for _, kv := range resp.Kvs {
  120. fileName := getNameFromKey(kv.Key)
  121. if fileName == "" {
  122. continue
  123. }
  124. if fileName == startFileName && !includeStartFile {
  125. continue
  126. }
  127. lastFileName = fileName
  128. limit--
  129. if limit < 0 {
  130. break
  131. }
  132. entry := &filer.Entry{
  133. FullPath: weed_util.NewFullPath(string(dirPath), fileName),
  134. }
  135. if decodeErr := entry.DecodeAttributesAndChunks(weed_util.MaybeDecompressData(kv.Value)); decodeErr != nil {
  136. err = decodeErr
  137. glog.V(0).Infof("list %s : %v", entry.FullPath, err)
  138. break
  139. }
  140. if !eachEntryFunc(entry) {
  141. break
  142. }
  143. }
  144. return lastFileName, err
  145. }
  146. func genKey(dirPath, fileName string) (key []byte) {
  147. key = []byte(dirPath)
  148. key = append(key, DIR_FILE_SEPARATOR)
  149. key = append(key, []byte(fileName)...)
  150. return key
  151. }
  152. func genDirectoryKeyPrefix(fullpath weed_util.FullPath, startFileName string) (keyPrefix []byte) {
  153. keyPrefix = []byte(string(fullpath))
  154. keyPrefix = append(keyPrefix, DIR_FILE_SEPARATOR)
  155. if len(startFileName) > 0 {
  156. keyPrefix = append(keyPrefix, []byte(startFileName)...)
  157. }
  158. return keyPrefix
  159. }
  160. func getNameFromKey(key []byte) string {
  161. sepIndex := len(key) - 1
  162. for sepIndex >= 0 && key[sepIndex] != DIR_FILE_SEPARATOR {
  163. sepIndex--
  164. }
  165. return string(key[sepIndex+1:])
  166. }
  167. func (store *EtcdStore) Shutdown() {
  168. store.client.Close()
  169. }