vitess.io/vitess@v0.16.2/go/vt/mysqlctl/cephbackupstorage/ceph.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package cephbackupstorage implements the BackupStorage interface
    18  // for Ceph Cloud Storage.
    19  package cephbackupstorage
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"os"
    28  	"sort"
    29  	"strings"
    30  	"sync"
    31  
    32  	minio "github.com/minio/minio-go"
    33  	"github.com/spf13/pflag"
    34  
    35  	"vitess.io/vitess/go/vt/concurrency"
    36  	"vitess.io/vitess/go/vt/log"
    37  	"vitess.io/vitess/go/vt/mysqlctl/backupstorage"
    38  	"vitess.io/vitess/go/vt/servenv"
    39  )
    40  
    41  var (
    42  	// configFilePath is where the configs/credentials for backups will be stored.
    43  	configFilePath string
    44  )
    45  
    46  func registerFlags(fs *pflag.FlagSet) {
    47  	fs.StringVar(&configFilePath, "ceph_backup_storage_config", "ceph_backup_config.json",
    48  		"Path to JSON config file for ceph backup storage.")
    49  }
    50  
    51  func init() {
    52  	servenv.OnParseFor("vtbackup", registerFlags)
    53  	servenv.OnParseFor("vtctl", registerFlags)
    54  	servenv.OnParseFor("vtctld", registerFlags)
    55  	servenv.OnParseFor("vttablet", registerFlags)
    56  }
    57  
    58  var storageConfig struct {
    59  	AccessKey string `json:"accessKey"`
    60  	SecretKey string `json:"secretKey"`
    61  	EndPoint  string `json:"endPoint"`
    62  	UseSSL    bool   `json:"useSSL"`
    63  }
    64  
    65  // CephBackupHandle implements BackupHandle for Ceph Cloud Storage.
    66  type CephBackupHandle struct {
    67  	client    *minio.Client
    68  	bs        *CephBackupStorage
    69  	dir       string
    70  	name      string
    71  	readOnly  bool
    72  	errors    concurrency.AllErrorRecorder
    73  	waitGroup sync.WaitGroup
    74  }
    75  
    76  // RecordError is part of the concurrency.ErrorRecorder interface.
    77  func (bh *CephBackupHandle) RecordError(err error) {
    78  	bh.errors.RecordError(err)
    79  }
    80  
    81  // HasErrors is part of the concurrency.ErrorRecorder interface.
    82  func (bh *CephBackupHandle) HasErrors() bool {
    83  	return bh.errors.HasErrors()
    84  }
    85  
    86  // Error is part of the concurrency.ErrorRecorder interface.
    87  func (bh *CephBackupHandle) Error() error {
    88  	return bh.errors.Error()
    89  }
    90  
    91  // Directory implements BackupHandle.
    92  func (bh *CephBackupHandle) Directory() string {
    93  	return bh.dir
    94  }
    95  
    96  // Name implements BackupHandle.
    97  func (bh *CephBackupHandle) Name() string {
    98  	return bh.name
    99  }
   100  
   101  // AddFile implements BackupHandle.
   102  func (bh *CephBackupHandle) AddFile(ctx context.Context, filename string, filesize int64) (io.WriteCloser, error) {
   103  	if bh.readOnly {
   104  		return nil, fmt.Errorf("AddFile cannot be called on read-only backup")
   105  	}
   106  	reader, writer := io.Pipe()
   107  	bh.waitGroup.Add(1)
   108  	go func() {
   109  		defer bh.waitGroup.Done()
   110  
   111  		// ceph bucket name is where the backups will go
   112  		//backup handle dir field contains keyspace/shard value
   113  		bucket := alterBucketName(bh.dir)
   114  
   115  		// Give PutObject() the read end of the pipe.
   116  		object := objName(bh.dir, bh.name, filename)
   117  		// If filesize is unknown, the caller should pass in -1 and we will pass it through.
   118  		_, err := bh.client.PutObjectWithContext(ctx, bucket, object, reader, filesize, minio.PutObjectOptions{ContentType: "application/octet-stream"})
   119  		if err != nil {
   120  			// Signal the writer that an error occurred, in case it's not done writing yet.
   121  			reader.CloseWithError(err)
   122  			// In case the error happened after the writer finished, we need to remember it.
   123  			bh.RecordError(err)
   124  		}
   125  	}()
   126  	// Give our caller the write end of the pipe.
   127  	return writer, nil
   128  }
   129  
   130  // EndBackup implements BackupHandle.
   131  func (bh *CephBackupHandle) EndBackup(ctx context.Context) error {
   132  	if bh.readOnly {
   133  		return fmt.Errorf("EndBackup cannot be called on read-only backup")
   134  	}
   135  	bh.waitGroup.Wait()
   136  	// Return the saved PutObject() errors, if any.
   137  	return bh.Error()
   138  }
   139  
   140  // AbortBackup implements BackupHandle.
   141  func (bh *CephBackupHandle) AbortBackup(ctx context.Context) error {
   142  	if bh.readOnly {
   143  		return fmt.Errorf("AbortBackup cannot be called on read-only backup")
   144  	}
   145  	return bh.bs.RemoveBackup(ctx, bh.dir, bh.name)
   146  }
   147  
   148  // ReadFile implements BackupHandle.
   149  func (bh *CephBackupHandle) ReadFile(ctx context.Context, filename string) (io.ReadCloser, error) {
   150  	if !bh.readOnly {
   151  		return nil, fmt.Errorf("ReadFile cannot be called on read-write backup")
   152  	}
   153  	// ceph bucket name
   154  	bucket := alterBucketName(bh.dir)
   155  	object := objName(bh.dir, bh.name, filename)
   156  	return bh.client.GetObjectWithContext(ctx, bucket, object, minio.GetObjectOptions{})
   157  }
   158  
   159  // CephBackupStorage implements BackupStorage for Ceph Cloud Storage.
   160  type CephBackupStorage struct {
   161  	// client is the instance of the Ceph Cloud Storage Go client.
   162  	// Once this field is set, it must not be written again/unset to nil.
   163  	_client *minio.Client
   164  	// mu guards all fields.
   165  	mu sync.Mutex
   166  }
   167  
   168  // ListBackups implements BackupStorage.
   169  func (bs *CephBackupStorage) ListBackups(ctx context.Context, dir string) ([]backupstorage.BackupHandle, error) {
   170  	c, err := bs.client()
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	// ceph bucket name
   175  	bucket := alterBucketName(dir)
   176  
   177  	// List prefixes that begin with dir (i.e. list subdirs).
   178  	var subdirs []string
   179  	searchPrefix := objName(dir, "")
   180  
   181  	doneCh := make(chan struct{})
   182  	for object := range c.ListObjects(bucket, searchPrefix, false, doneCh) {
   183  		if object.Err != nil {
   184  			_, err := c.BucketExists(bucket)
   185  			if err != nil {
   186  				return nil, nil
   187  			}
   188  			return nil, object.Err
   189  		}
   190  		subdir := strings.TrimPrefix(object.Key, searchPrefix)
   191  		subdir = strings.TrimSuffix(subdir, "/")
   192  		subdirs = append(subdirs, subdir)
   193  	}
   194  
   195  	// Backups must be returned in order, oldest first.
   196  	sort.Strings(subdirs)
   197  
   198  	result := make([]backupstorage.BackupHandle, 0, len(subdirs))
   199  	for _, subdir := range subdirs {
   200  		result = append(result, &CephBackupHandle{
   201  			client:   c,
   202  			bs:       bs,
   203  			dir:      dir,
   204  			name:     subdir,
   205  			readOnly: true,
   206  		})
   207  	}
   208  	return result, nil
   209  }
   210  
   211  // StartBackup implements BackupStorage.
   212  func (bs *CephBackupStorage) StartBackup(ctx context.Context, dir, name string) (backupstorage.BackupHandle, error) {
   213  	c, err := bs.client()
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	// ceph bucket name
   218  	bucket := alterBucketName(dir)
   219  
   220  	found, err := c.BucketExists(bucket)
   221  
   222  	if err != nil {
   223  		log.Info("Error from BucketExists: %v, quitting", bucket)
   224  		return nil, errors.New("Error checking whether bucket exists: " + bucket)
   225  	}
   226  	if !found {
   227  		log.Info("Bucket: %v doesn't exist, creating new bucket with the required name", bucket)
   228  		err = c.MakeBucket(bucket, "")
   229  		if err != nil {
   230  			log.Info("Error creating Bucket: %v, quitting", bucket)
   231  			return nil, errors.New("Error creating new bucket: " + bucket)
   232  		}
   233  	}
   234  
   235  	return &CephBackupHandle{
   236  		client:   c,
   237  		bs:       bs,
   238  		dir:      dir,
   239  		name:     name,
   240  		readOnly: false,
   241  	}, nil
   242  }
   243  
   244  // RemoveBackup implements BackupStorage.
   245  func (bs *CephBackupStorage) RemoveBackup(ctx context.Context, dir, name string) error {
   246  	c, err := bs.client()
   247  	if err != nil {
   248  		return err
   249  	}
   250  	// ceph bucket name
   251  	bucket := alterBucketName(dir)
   252  
   253  	fullName := objName(dir, name, "")
   254  	var arr []string
   255  	doneCh := make(chan struct{})
   256  	defer close(doneCh)
   257  	for object := range c.ListObjects(bucket, fullName, true, doneCh) {
   258  		if object.Err != nil {
   259  			return object.Err
   260  		}
   261  		arr = append(arr, object.Key)
   262  	}
   263  	for _, obj := range arr {
   264  		err = c.RemoveObject(bucket, obj)
   265  		if err != nil {
   266  			return err
   267  		}
   268  	}
   269  	return nil
   270  }
   271  
   272  // Close implements BackupStorage.
   273  func (bs *CephBackupStorage) Close() error {
   274  	bs.mu.Lock()
   275  	defer bs.mu.Unlock()
   276  
   277  	if bs._client != nil {
   278  		// a new client the next time one is needed.
   279  		bs._client = nil
   280  	}
   281  	return nil
   282  }
   283  
   284  // client returns the Ceph Storage client instance.
   285  // If there isn't one yet, it tries to create one.
   286  func (bs *CephBackupStorage) client() (*minio.Client, error) {
   287  	bs.mu.Lock()
   288  	defer bs.mu.Unlock()
   289  
   290  	if bs._client == nil {
   291  		configFile, err := os.Open(configFilePath)
   292  		if err != nil {
   293  			return nil, fmt.Errorf("file not present : %v", err)
   294  		}
   295  		defer configFile.Close()
   296  		jsonParser := json.NewDecoder(configFile)
   297  		if err = jsonParser.Decode(&storageConfig); err != nil {
   298  			return nil, fmt.Errorf("error parsing the json file : %v", err)
   299  		}
   300  
   301  		accessKey := storageConfig.AccessKey
   302  		secretKey := storageConfig.SecretKey
   303  		url := storageConfig.EndPoint
   304  		useSSL := storageConfig.UseSSL
   305  
   306  		client, err := minio.NewV2(url, accessKey, secretKey, useSSL)
   307  		if err != nil {
   308  			return nil, err
   309  		}
   310  		bs._client = client
   311  	}
   312  	return bs._client, nil
   313  }
   314  
   315  func init() {
   316  	backupstorage.BackupStorageMap["ceph"] = &CephBackupStorage{}
   317  }
   318  
   319  // objName joins path parts into an object name.
   320  // Unlike path.Join, it doesn't collapse ".." or strip trailing slashes.
   321  func objName(parts ...string) string {
   322  	return strings.Join(parts, "/")
   323  }
   324  
   325  // keeping in view the bucket naming conventions for ceph
   326  // only keyspace informations is extracted and used for bucket name
   327  func alterBucketName(dir string) string {
   328  	bucket := strings.ToLower(dir)
   329  	bucket = strings.Split(bucket, "/")[0]
   330  	bucket = strings.Replace(bucket, "_", "-", -1)
   331  	return bucket
   332  }