arangodb_store.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. package arangodb
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "time"
  10. "github.com/arangodb/go-driver"
  11. "github.com/arangodb/go-driver/http"
  12. "github.com/seaweedfs/seaweedfs/weed/filer"
  13. "github.com/seaweedfs/seaweedfs/weed/glog"
  14. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  15. "github.com/seaweedfs/seaweedfs/weed/util"
  16. )
  17. func init() {
  18. filer.Stores = append(filer.Stores, &ArangodbStore{})
  19. }
  20. var (
  21. BUCKET_PREFIX = "/buckets"
  22. DEFAULT_COLLECTION = "seaweed_no_bucket"
  23. KVMETA_COLLECTION = "seaweed_kvmeta"
  24. )
  25. type ArangodbStore struct {
  26. connect driver.Connection
  27. client driver.Client
  28. database driver.Database
  29. kvCollection driver.Collection
  30. buckets map[string]driver.Collection
  31. mu sync.RWMutex
  32. databaseName string
  33. }
  34. type Model struct {
  35. Key string `json:"_key"`
  36. Directory string `json:"directory,omitempty"`
  37. Name string `json:"name,omitempty"`
  38. Ttl string `json:"ttl,omitempty"`
  39. //arangodb does not support binary blobs
  40. //we encode byte slice into uint64 slice
  41. //see helpers.go
  42. Meta []uint64 `json:"meta"`
  43. }
  44. func (store *ArangodbStore) GetName() string {
  45. return "arangodb"
  46. }
  47. func (store *ArangodbStore) Initialize(configuration util.Configuration, prefix string) (err error) {
  48. store.buckets = make(map[string]driver.Collection, 3)
  49. store.databaseName = configuration.GetString(prefix + "db_name")
  50. return store.connection(configuration.GetStringSlice(prefix+"servers"),
  51. configuration.GetString(prefix+"username"),
  52. configuration.GetString(prefix+"password"),
  53. configuration.GetBool(prefix+"insecure_skip_verify"),
  54. )
  55. }
  56. func (store *ArangodbStore) connection(uris []string, user string, pass string, insecure bool) (err error) {
  57. ctx, cn := context.WithTimeout(context.Background(), 10*time.Second)
  58. defer cn()
  59. store.connect, err = http.NewConnection(http.ConnectionConfig{
  60. Endpoints: uris,
  61. TLSConfig: &tls.Config{
  62. InsecureSkipVerify: insecure,
  63. },
  64. })
  65. if err != nil {
  66. return err
  67. }
  68. store.client, err = driver.NewClient(driver.ClientConfig{
  69. Connection: store.connect,
  70. Authentication: driver.BasicAuthentication(user, pass),
  71. })
  72. if err != nil {
  73. return err
  74. }
  75. ok, err := store.client.DatabaseExists(ctx, store.databaseName)
  76. if err != nil {
  77. return err
  78. }
  79. if ok {
  80. store.database, err = store.client.Database(ctx, store.databaseName)
  81. } else {
  82. store.database, err = store.client.CreateDatabase(ctx, store.databaseName, &driver.CreateDatabaseOptions{})
  83. }
  84. if err != nil {
  85. return err
  86. }
  87. if store.kvCollection, err = store.ensureCollection(ctx, KVMETA_COLLECTION); err != nil {
  88. return err
  89. }
  90. return err
  91. }
  92. type key int
  93. const (
  94. transactionKey key = 0
  95. )
  96. func (store *ArangodbStore) BeginTransaction(ctx context.Context) (context.Context, error) {
  97. keys := make([]string, 0, len(store.buckets)+1)
  98. for k := range store.buckets {
  99. keys = append(keys, k)
  100. }
  101. keys = append(keys, store.kvCollection.Name())
  102. txn, err := store.database.BeginTransaction(ctx, driver.TransactionCollections{
  103. Exclusive: keys,
  104. }, &driver.BeginTransactionOptions{})
  105. if err != nil {
  106. return nil, err
  107. }
  108. return context.WithValue(driver.WithTransactionID(ctx, txn), transactionKey, txn), nil
  109. }
  110. func (store *ArangodbStore) CommitTransaction(ctx context.Context) error {
  111. val := ctx.Value(transactionKey)
  112. cast, ok := val.(driver.TransactionID)
  113. if !ok {
  114. return fmt.Errorf("txn cast fail %s:", val)
  115. }
  116. err := store.database.CommitTransaction(ctx, cast, &driver.CommitTransactionOptions{})
  117. if err != nil {
  118. return err
  119. }
  120. return nil
  121. }
  122. func (store *ArangodbStore) RollbackTransaction(ctx context.Context) error {
  123. val := ctx.Value(transactionKey)
  124. cast, ok := val.(driver.TransactionID)
  125. if !ok {
  126. return fmt.Errorf("txn cast fail %s:", val)
  127. }
  128. err := store.database.AbortTransaction(ctx, cast, &driver.AbortTransactionOptions{})
  129. if err != nil {
  130. return err
  131. }
  132. return nil
  133. }
  134. func (store *ArangodbStore) InsertEntry(ctx context.Context, entry *filer.Entry) (err error) {
  135. dir, name := entry.FullPath.DirAndName()
  136. meta, err := entry.EncodeAttributesAndChunks()
  137. if err != nil {
  138. return fmt.Errorf("encode %s: %s", entry.FullPath, err)
  139. }
  140. if len(entry.GetChunks()) > filer.CountEntryChunksForGzip {
  141. meta = util.MaybeGzipData(meta)
  142. }
  143. model := &Model{
  144. Key: hashString(string(entry.FullPath)),
  145. Directory: dir,
  146. Name: name,
  147. Meta: bytesToArray(meta),
  148. }
  149. if entry.TtlSec > 0 {
  150. model.Ttl = time.Now().Add(time.Second * time.Duration(entry.TtlSec)).Format(time.RFC3339)
  151. } else {
  152. model.Ttl = ""
  153. }
  154. targetCollection, err := store.extractBucketCollection(ctx, entry.FullPath)
  155. if err != nil {
  156. return err
  157. }
  158. _, err = targetCollection.CreateDocument(ctx, model)
  159. if driver.IsConflict(err) {
  160. return store.UpdateEntry(ctx, entry)
  161. }
  162. if err != nil {
  163. return fmt.Errorf("InsertEntry %s: %v", entry.FullPath, err)
  164. }
  165. return nil
  166. }
  167. func (store *ArangodbStore) UpdateEntry(ctx context.Context, entry *filer.Entry) (err error) {
  168. dir, name := entry.FullPath.DirAndName()
  169. meta, err := entry.EncodeAttributesAndChunks()
  170. if err != nil {
  171. return fmt.Errorf("encode %s: %s", entry.FullPath, err)
  172. }
  173. if len(entry.GetChunks()) > filer.CountEntryChunksForGzip {
  174. meta = util.MaybeGzipData(meta)
  175. }
  176. model := &Model{
  177. Key: hashString(string(entry.FullPath)),
  178. Directory: dir,
  179. Name: name,
  180. Meta: bytesToArray(meta),
  181. }
  182. if entry.TtlSec > 0 {
  183. model.Ttl = time.Now().Add(time.Duration(entry.TtlSec) * time.Second).Format(time.RFC3339)
  184. } else {
  185. model.Ttl = "none"
  186. }
  187. targetCollection, err := store.extractBucketCollection(ctx, entry.FullPath)
  188. if err != nil {
  189. return err
  190. }
  191. _, err = targetCollection.UpdateDocument(ctx, model.Key, model)
  192. if err != nil {
  193. return fmt.Errorf("UpdateEntry %s: %v", entry.FullPath, err)
  194. }
  195. return nil
  196. }
  197. func (store *ArangodbStore) FindEntry(ctx context.Context, fullpath util.FullPath) (entry *filer.Entry, err error) {
  198. var data Model
  199. targetCollection, err := store.extractBucketCollection(ctx, fullpath)
  200. if err != nil {
  201. return nil, err
  202. }
  203. _, err = targetCollection.ReadDocument(ctx, hashString(string(fullpath)), &data)
  204. if err != nil {
  205. if driver.IsNotFound(err) {
  206. return nil, filer_pb.ErrNotFound
  207. }
  208. glog.Errorf("find %s: %v", fullpath, err)
  209. return nil, filer_pb.ErrNotFound
  210. }
  211. if len(data.Meta) == 0 {
  212. return nil, filer_pb.ErrNotFound
  213. }
  214. entry = &filer.Entry{
  215. FullPath: fullpath,
  216. }
  217. err = entry.DecodeAttributesAndChunks(util.MaybeDecompressData(arrayToBytes(data.Meta)))
  218. if err != nil {
  219. return entry, fmt.Errorf("decode %s : %v", entry.FullPath, err)
  220. }
  221. return entry, nil
  222. }
  223. func (store *ArangodbStore) DeleteEntry(ctx context.Context, fullpath util.FullPath) (err error) {
  224. targetCollection, err := store.extractBucketCollection(ctx, fullpath)
  225. if err != nil {
  226. return err
  227. }
  228. _, err = targetCollection.RemoveDocument(ctx, hashString(string(fullpath)))
  229. if err != nil && !driver.IsNotFound(err) {
  230. glog.Errorf("find %s: %v", fullpath, err)
  231. return fmt.Errorf("delete %s : %v", fullpath, err)
  232. }
  233. return nil
  234. }
  235. // this runs in log time
  236. func (store *ArangodbStore) DeleteFolderChildren(ctx context.Context, fullpath util.FullPath) (err error) {
  237. var query string
  238. targetCollection, err := store.extractBucketCollection(ctx, fullpath)
  239. if err != nil {
  240. return err
  241. }
  242. query = query + fmt.Sprintf(`
  243. for d in %s
  244. filter starts_with(d.directory, "%s/") || d.directory == "%s"
  245. remove d._key in %s`,
  246. "`"+targetCollection.Name()+"`",
  247. strings.Join(strings.Split(string(fullpath), "/"), ","),
  248. string(fullpath),
  249. "`"+targetCollection.Name()+"`",
  250. )
  251. cur, err := store.database.Query(ctx, query, nil)
  252. if err != nil {
  253. return fmt.Errorf("delete %s : %v", fullpath, err)
  254. }
  255. defer cur.Close()
  256. return nil
  257. }
  258. func (store *ArangodbStore) ListDirectoryEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
  259. return store.ListDirectoryPrefixedEntries(ctx, dirPath, startFileName, includeStartFile, limit, "", eachEntryFunc)
  260. }
  261. func (store *ArangodbStore) ListDirectoryPrefixedEntries(ctx context.Context, dirPath util.FullPath, startFileName string, includeStartFile bool, limit int64, prefix string, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
  262. targetCollection, err := store.extractBucketCollection(ctx, dirPath+"/")
  263. if err != nil {
  264. return lastFileName, err
  265. }
  266. query := "for d in " + "`" + targetCollection.Name() + "`"
  267. if includeStartFile {
  268. query = query + " filter d.name >= \"" + startFileName + "\" "
  269. } else {
  270. query = query + " filter d.name > \"" + startFileName + "\" "
  271. }
  272. if prefix != "" {
  273. query = query + fmt.Sprintf(`&& starts_with(d.name, "%s")`, prefix)
  274. }
  275. query = query + `
  276. filter d.directory == @dir
  277. sort d.name asc
  278. `
  279. if limit > 0 {
  280. query = query + "limit " + strconv.Itoa(int(limit))
  281. }
  282. query = query + "\n return d"
  283. cur, err := store.database.Query(ctx, query, map[string]interface{}{"dir": dirPath})
  284. if err != nil {
  285. return lastFileName, fmt.Errorf("failed to list directory entries: find error: %w", err)
  286. }
  287. defer cur.Close()
  288. for cur.HasMore() {
  289. var data Model
  290. _, err = cur.ReadDocument(ctx, &data)
  291. if err != nil {
  292. break
  293. }
  294. entry := &filer.Entry{
  295. FullPath: util.NewFullPath(data.Directory, data.Name),
  296. }
  297. lastFileName = data.Name
  298. converted := arrayToBytes(data.Meta)
  299. if decodeErr := entry.DecodeAttributesAndChunks(util.MaybeDecompressData(converted)); decodeErr != nil {
  300. err = decodeErr
  301. glog.V(0).Infof("list %s : %v", entry.FullPath, err)
  302. break
  303. }
  304. if !eachEntryFunc(entry) {
  305. break
  306. }
  307. }
  308. return lastFileName, err
  309. }
  310. func (store *ArangodbStore) Shutdown() {
  311. }