milvus/internal/kv/minio/minio_kv.go
godchen c8c10bb3d6
Remove performanceTest (#8164)
Signed-off-by: godchen <qingxiang.chen@zilliz.com>
2021-09-18 10:13:52 +08:00

262 lines
6.8 KiB
Go

// Copyright (C) 2019-2020 Zilliz. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under the License.
package miniokv
import (
"context"
"fmt"
"sync"
"io"
"strings"
"github.com/milvus-io/milvus/internal/log"
"github.com/milvus-io/milvus/internal/util/retry"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"go.uber.org/zap"
)
type MinIOKV struct {
ctx context.Context
minioClient *minio.Client
bucketName string
}
type Option struct {
Address string
AccessKeyID string
BucketName string
SecretAccessKeyID string
UseSSL bool
CreateBucket bool // when bucket not existed, create it
}
// NewMinIOKV creates MinIOKV to save and load object to MinIOKV.
func NewMinIOKV(ctx context.Context, option *Option) (*MinIOKV, error) {
var minIOClient *minio.Client
var err error
log.Debug("MinioKV NewMinioKV", zap.Any("option", option))
minIOClient, err = minio.New(option.Address, &minio.Options{
Creds: credentials.NewStaticV4(option.AccessKeyID, option.SecretAccessKeyID, ""),
Secure: option.UseSSL,
})
// options nil or invalid formatted endpoint, don't need retry
if err != nil {
return nil, err
}
var bucketExists bool
// check valid in first query
checkBucketFn := func() error {
bucketExists, err = minIOClient.BucketExists(ctx, option.BucketName)
if err != nil {
return err
}
if !bucketExists {
log.Debug("MinioKV NewMinioKV", zap.Any("Check bucket", "bucket not exist"))
if option.CreateBucket {
log.Debug("MinioKV NewMinioKV create bucket.")
return minIOClient.MakeBucket(ctx, option.BucketName, minio.MakeBucketOptions{})
}
return fmt.Errorf("bucket %s not Existed", option.BucketName)
}
return nil
}
err = retry.Do(ctx, checkBucketFn, retry.Attempts(300))
if err != nil {
return nil, err
}
kv := &MinIOKV{
ctx: ctx,
minioClient: minIOClient,
bucketName: option.BucketName,
}
log.Debug("MinioKV new MinioKV success.")
return kv, nil
}
// Exist check whether a key exists in MinIO.
func (kv *MinIOKV) Exist(key string) bool {
_, err := kv.minioClient.StatObject(kv.ctx, kv.bucketName, key, minio.StatObjectOptions{})
return err == nil
}
// LoadWithPrefix load objects with the same prefix @key from minio .
func (kv *MinIOKV) LoadWithPrefix(key string) ([]string, []string, error) {
objects := kv.minioClient.ListObjects(kv.ctx, kv.bucketName, minio.ListObjectsOptions{Prefix: key})
var objectsKeys []string
var objectsValues []string
for object := range objects {
objectsKeys = append(objectsKeys, object.Key)
}
objectsValues, err := kv.MultiLoad(objectsKeys)
if err != nil {
log.Debug("MinIO", zap.String("cannot load value with prefix:%s", key))
}
return objectsKeys, objectsValues, nil
}
// LoadWithPrefix load an object with @key.
func (kv *MinIOKV) Load(key string) (string, error) {
object, err := kv.minioClient.GetObject(kv.ctx, kv.bucketName, key, minio.GetObjectOptions{})
if object != nil {
defer object.Close()
}
if err != nil {
return "", err
}
buf := new(strings.Builder)
_, err = io.Copy(buf, object)
if err != nil && err != io.EOF {
return "", err
}
return buf.String(), nil
}
// FGetObject download file from minio to local storage system.
func (kv *MinIOKV) FGetObject(key, localPath string) error {
err := kv.minioClient.FGetObject(kv.ctx, kv.bucketName, key, localPath+key, minio.GetObjectOptions{})
if err != nil {
return err
}
return nil
}
// FGetObjects download file from minio to local storage system.
// For parallell downloads file, n goroutines will be started to download n keys.
func (kv *MinIOKV) FGetObjects(keys []string, localPath string) error {
var wg sync.WaitGroup
el := make(errorList, len(keys))
for i, key := range keys {
wg.Add(1)
go func(i int, key string) {
err := kv.minioClient.FGetObject(kv.ctx, kv.bucketName, key, localPath+key, minio.GetObjectOptions{})
if err != nil {
el[i] = err
}
wg.Done()
}(i, key)
}
wg.Wait()
for _, err := range el {
if err != nil {
return el
}
}
return nil
}
// MultiLoad loads objects with multi @keys.
func (kv *MinIOKV) MultiLoad(keys []string) ([]string, error) {
var resultErr error
var objectsValues []string
for _, key := range keys {
objectValue, err := kv.Load(key)
if err != nil {
if resultErr == nil {
resultErr = err
}
}
objectsValues = append(objectsValues, objectValue)
}
return objectsValues, resultErr
}
// Save object with @key to Minio. Object value is @value.
func (kv *MinIOKV) Save(key, value string) error {
reader := strings.NewReader(value)
_, err := kv.minioClient.PutObject(kv.ctx, kv.bucketName, key, reader, int64(len(value)), minio.PutObjectOptions{})
if err != nil {
return err
}
return err
}
// Save MultiObject, the path is the key of @kvs. The object value is the value
// of @kvs.
func (kv *MinIOKV) MultiSave(kvs map[string]string) error {
var resultErr error
for key, value := range kvs {
err := kv.Save(key, value)
if err != nil {
if resultErr == nil {
resultErr = err
}
}
}
return resultErr
}
// LoadWithPrefix remove all objects with the same prefix @prefix from minio .
func (kv *MinIOKV) RemoveWithPrefix(prefix string) error {
objectsCh := make(chan minio.ObjectInfo)
go func() {
defer close(objectsCh)
for object := range kv.minioClient.ListObjects(kv.ctx, kv.bucketName, minio.ListObjectsOptions{Prefix: prefix}) {
objectsCh <- object
}
}()
for rErr := range kv.minioClient.RemoveObjects(kv.ctx, kv.bucketName, objectsCh, minio.RemoveObjectsOptions{GovernanceBypass: true}) {
if rErr.Err != nil {
return rErr.Err
}
}
return nil
}
// Remove delete an object with @key.
func (kv *MinIOKV) Remove(key string) error {
err := kv.minioClient.RemoveObject(kv.ctx, kv.bucketName, string(key), minio.RemoveObjectOptions{})
return err
}
// MultiRemove delete a objects with @keys.
func (kv *MinIOKV) MultiRemove(keys []string) error {
var resultErr error
for _, key := range keys {
err := kv.Remove(key)
if err != nil {
if resultErr == nil {
resultErr = err
}
}
}
return resultErr
}
func (kv *MinIOKV) Close() {
}
type errorList []error
func (el errorList) Error() string {
var builder strings.Builder
builder.WriteString("All downloads results:\n")
for index, err := range el {
builder.WriteString(fmt.Sprintf("downloads #%d:%s\n", index+1, err.Error()))
}
return builder.String()
}