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 }