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.

236 lines
5.8 KiB

5 years ago
5 years ago
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/filer2"
  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. filer2.Stores = append(filer2.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 *filer2.Entry) (err error) {
  61. key := genKey(entry.DirAndName())
  62. value, err := entry.EncodeAttributesAndChunks()
  63. if err != nil {
  64. return fmt.Errorf("encoding %s %+v: %v", entry.FullPath, entry.Attr, err)
  65. }
  66. if _, err := store.client.Put(ctx, string(key), string(value)); err != nil {
  67. return fmt.Errorf("persisting %s : %v", entry.FullPath, err)
  68. }
  69. return nil
  70. }
  71. func (store *EtcdStore) UpdateEntry(ctx context.Context, entry *filer2.Entry) (err error) {
  72. return store.InsertEntry(ctx, entry)
  73. }
  74. func (store *EtcdStore) FindEntry(ctx context.Context, fullpath weed_util.FullPath) (entry *filer2.Entry, err error) {
  75. key := genKey(fullpath.DirAndName())
  76. resp, err := store.client.Get(ctx, string(key))
  77. if err != nil {
  78. return nil, fmt.Errorf("get %s : %v", entry.FullPath, err)
  79. }
  80. if len(resp.Kvs) == 0 {
  81. return nil, filer_pb.ErrNotFound
  82. }
  83. entry = &filer2.Entry{
  84. FullPath: fullpath,
  85. }
  86. err = entry.DecodeAttributesAndChunks(resp.Kvs[0].Value)
  87. if err != nil {
  88. return entry, fmt.Errorf("decode %s : %v", entry.FullPath, err)
  89. }
  90. return entry, nil
  91. }
  92. func (store *EtcdStore) DeleteEntry(ctx context.Context, fullpath weed_util.FullPath) (err error) {
  93. key := genKey(fullpath.DirAndName())
  94. if _, err := store.client.Delete(ctx, string(key)); err != nil {
  95. return fmt.Errorf("delete %s : %v", fullpath, err)
  96. }
  97. return nil
  98. }
  99. func (store *EtcdStore) DeleteFolderChildren(ctx context.Context, fullpath weed_util.FullPath) (err error) {
  100. directoryPrefix := genDirectoryKeyPrefix(fullpath, "")
  101. if _, err := store.client.Delete(ctx, string(directoryPrefix), clientv3.WithPrefix()); err != nil {
  102. return fmt.Errorf("deleteFolderChildren %s : %v", fullpath, err)
  103. }
  104. return nil
  105. }
  106. func (store *EtcdStore) ListDirectoryPrefixedEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int, prefix string) (entries []*filer2.Entry, err error) {
  107. count := 0
  108. notPrefixed, err := store.ListDirectoryEntries(ctx, fullpath, startFileName, inclusive, limit)
  109. if err != nil {
  110. return nil, err
  111. }
  112. if prefix == "" {
  113. return notPrefixed, nil
  114. }
  115. var lastFileName string
  116. for count < limit {
  117. for _, entry := range notPrefixed {
  118. lastFileName = entry.Name()
  119. if strings.HasPrefix(entry.Name(), prefix) {
  120. count++
  121. entries = append(entries, entry)
  122. }
  123. }
  124. if count >= limit {
  125. break
  126. }
  127. notPrefixed, err = store.ListDirectoryEntries(ctx, fullpath, lastFileName, inclusive, limit)
  128. if err != nil {
  129. return nil, err
  130. }
  131. if len(notPrefixed) == 0 {
  132. break
  133. }
  134. }
  135. return entries, nil
  136. }
  137. func (store *EtcdStore) ListDirectoryEntries(ctx context.Context, fullpath weed_util.FullPath, startFileName string, inclusive bool, limit int) (entries []*filer2.Entry, err error) {
  138. directoryPrefix := genDirectoryKeyPrefix(fullpath, "")
  139. resp, err := store.client.Get(ctx, string(directoryPrefix),
  140. clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortDescend))
  141. if err != nil {
  142. return nil, fmt.Errorf("list %s : %v", fullpath, err)
  143. }
  144. for _, kv := range resp.Kvs {
  145. fileName := getNameFromKey(kv.Key)
  146. if fileName == "" {
  147. continue
  148. }
  149. if fileName == startFileName && !inclusive {
  150. continue
  151. }
  152. limit--
  153. if limit < 0 {
  154. break
  155. }
  156. entry := &filer2.Entry{
  157. FullPath: weed_util.NewFullPath(string(fullpath), fileName),
  158. }
  159. if decodeErr := entry.DecodeAttributesAndChunks(kv.Value); decodeErr != nil {
  160. err = decodeErr
  161. glog.V(0).Infof("list %s : %v", entry.FullPath, err)
  162. break
  163. }
  164. entries = append(entries, entry)
  165. }
  166. return entries, err
  167. }
  168. func genKey(dirPath, fileName string) (key []byte) {
  169. key = []byte(dirPath)
  170. key = append(key, DIR_FILE_SEPARATOR)
  171. key = append(key, []byte(fileName)...)
  172. return key
  173. }
  174. func genDirectoryKeyPrefix(fullpath weed_util.FullPath, startFileName string) (keyPrefix []byte) {
  175. keyPrefix = []byte(string(fullpath))
  176. keyPrefix = append(keyPrefix, DIR_FILE_SEPARATOR)
  177. if len(startFileName) > 0 {
  178. keyPrefix = append(keyPrefix, []byte(startFileName)...)
  179. }
  180. return keyPrefix
  181. }
  182. func getNameFromKey(key []byte) string {
  183. sepIndex := len(key) - 1
  184. for sepIndex >= 0 && key[sepIndex] != DIR_FILE_SEPARATOR {
  185. sepIndex--
  186. }
  187. return string(key[sepIndex+1:])
  188. }
  189. func (store *EtcdStore) Shutdown() {
  190. store.client.Close()
  191. }