github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/ee/backup/file_handler.go (about)

     1  // +build !oss
     2  
     3  /*
     4   * Copyright 2018 Dgraph Labs, Inc. and Contributors
     5   *
     6   * Licensed under the Dgraph Community License (the "License"); you
     7   * may not use this file except in compliance with the License. You
     8   * may obtain a copy of the License at
     9   *
    10   *     https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt
    11   */
    12  
    13  package backup
    14  
    15  import (
    16  	"encoding/json"
    17  	"fmt"
    18  	"io/ioutil"
    19  	"net/url"
    20  	"os"
    21  	"path/filepath"
    22  	"sort"
    23  	"strings"
    24  
    25  	"github.com/dgraph-io/dgraph/protos/pb"
    26  	"github.com/dgraph-io/dgraph/x"
    27  
    28  	"github.com/golang/glog"
    29  	"github.com/pkg/errors"
    30  )
    31  
    32  // fileHandler is used for 'file:' URI scheme.
    33  type fileHandler struct {
    34  	fp *os.File
    35  }
    36  
    37  // readManifest reads a manifest file at path using the handler.
    38  // Returns nil on success, otherwise an error.
    39  func (h *fileHandler) readManifest(path string, m *Manifest) error {
    40  	b, err := ioutil.ReadFile(path)
    41  	if err != nil {
    42  		return err
    43  	}
    44  	return json.Unmarshal(b, m)
    45  }
    46  
    47  func (h *fileHandler) createFiles(uri *url.URL, req *pb.BackupRequest, fileName string) error {
    48  	var dir, path string
    49  
    50  	dir = filepath.Join(uri.Path, fmt.Sprintf(backupPathFmt, req.UnixTs))
    51  	err := os.Mkdir(dir, 0700)
    52  	if err != nil && !os.IsExist(err) {
    53  		return err
    54  	}
    55  
    56  	path = filepath.Join(dir, fileName)
    57  	h.fp, err = os.Create(path)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	glog.V(2).Infof("Using file path: %q", path)
    62  	return nil
    63  }
    64  
    65  // GetLatestManifest reads the manifests at the given URL and returns the
    66  // latest manifest.
    67  func (h *fileHandler) GetLatestManifest(uri *url.URL) (*Manifest, error) {
    68  	if !pathExist(uri.Path) {
    69  		return nil, errors.Errorf("The path %q does not exist or it is inaccessible.", uri.Path)
    70  	}
    71  
    72  	// Find the max Since value from the latest backup.
    73  	var lastManifest string
    74  	suffix := filepath.Join(string(filepath.Separator), backupManifest)
    75  	_ = x.WalkPathFunc(uri.Path, func(path string, isdir bool) bool {
    76  		if !isdir && strings.HasSuffix(path, suffix) && path > lastManifest {
    77  			lastManifest = path
    78  		}
    79  		return false
    80  	})
    81  
    82  	var m Manifest
    83  	if lastManifest == "" {
    84  		return &m, nil
    85  	}
    86  
    87  	if err := h.readManifest(lastManifest, &m); err != nil {
    88  		return nil, err
    89  	}
    90  	return &m, nil
    91  }
    92  
    93  // CreateBackupFile prepares the a path to save the backup file.
    94  func (h *fileHandler) CreateBackupFile(uri *url.URL, req *pb.BackupRequest) error {
    95  	if !pathExist(uri.Path) {
    96  		return errors.Errorf("The path %q does not exist or it is inaccessible.", uri.Path)
    97  	}
    98  
    99  	fileName := backupName(req.ReadTs, req.GroupId)
   100  	return h.createFiles(uri, req, fileName)
   101  }
   102  
   103  // CreateManifest completes the backup by writing the manifest to a file.
   104  func (h *fileHandler) CreateManifest(uri *url.URL, req *pb.BackupRequest) error {
   105  	if !pathExist(uri.Path) {
   106  		return errors.Errorf("The path %q does not exist or it is inaccessible.", uri.Path)
   107  	}
   108  
   109  	return h.createFiles(uri, req, backupManifest)
   110  }
   111  
   112  // Load uses tries to load any backup files found.
   113  // Returns the maximum value of Since on success, error otherwise.
   114  func (h *fileHandler) Load(uri *url.URL, backupId string, fn loadFn) (uint64, error) {
   115  	if !pathExist(uri.Path) {
   116  		return 0, errors.Errorf("The path %q does not exist or it is inaccessible.", uri.Path)
   117  	}
   118  
   119  	suffix := filepath.Join(string(filepath.Separator), backupManifest)
   120  	paths := x.WalkPathFunc(uri.Path, func(path string, isdir bool) bool {
   121  		return !isdir && strings.HasSuffix(path, suffix)
   122  	})
   123  	if len(paths) == 0 {
   124  		return 0, errors.Errorf("No manifests found at path: %s", uri.Path)
   125  	}
   126  	sort.Strings(paths)
   127  	if glog.V(3) {
   128  		fmt.Printf("Found backup manifest(s): %v\n", paths)
   129  	}
   130  
   131  	// Read and filter the files to get the list of files to consider
   132  	// for this restore operation.
   133  	var manifests []*Manifest
   134  	for _, path := range paths {
   135  		var m Manifest
   136  		if err := h.readManifest(path, &m); err != nil {
   137  			return 0, errors.Wrapf(err, "While reading %q", path)
   138  		}
   139  		m.Path = path
   140  		manifests = append(manifests, &m)
   141  	}
   142  	manifests, err := filterManifests(manifests, backupId)
   143  	if err != nil {
   144  		return 0, err
   145  	}
   146  
   147  	// Process each manifest, first check that they are valid and then confirm the
   148  	// backup files for each group exist. Each group in manifest must have a backup file,
   149  	// otherwise this is a failure and the user must remedy.
   150  	var since uint64
   151  	for i, manifest := range manifests {
   152  		if manifest.Since == 0 || len(manifest.Groups) == 0 {
   153  			if glog.V(2) {
   154  				fmt.Printf("Restore: skip backup: %#v\n", manifest)
   155  			}
   156  			continue
   157  		}
   158  
   159  		path := filepath.Dir(manifests[i].Path)
   160  		for gid := range manifest.Groups {
   161  			file := filepath.Join(path, backupName(manifest.Since, gid))
   162  			fp, err := os.Open(file)
   163  			if err != nil {
   164  				return 0, errors.Wrapf(err, "Failed to open %q", file)
   165  			}
   166  			defer fp.Close()
   167  
   168  			// Only restore the predicates that were assigned to this group at the time
   169  			// of the last backup.
   170  			predSet := manifests[len(manifests)-1].getPredsInGroup(gid)
   171  			if err = fn(fp, int(gid), predSet); err != nil {
   172  				return 0, err
   173  			}
   174  		}
   175  		since = manifest.Since
   176  	}
   177  	return since, nil
   178  }
   179  
   180  // ListManifests loads the manifests in the locations and returns them.
   181  func (h *fileHandler) ListManifests(uri *url.URL) ([]string, error) {
   182  	if !pathExist(uri.Path) {
   183  		return nil, errors.Errorf("The path %q does not exist or it is inaccessible.", uri.Path)
   184  	}
   185  
   186  	suffix := filepath.Join(string(filepath.Separator), backupManifest)
   187  	manifests := x.WalkPathFunc(uri.Path, func(path string, isdir bool) bool {
   188  		return !isdir && strings.HasSuffix(path, suffix)
   189  	})
   190  	if len(manifests) == 0 {
   191  		return nil, errors.Errorf("No manifests found at path: %s", uri.Path)
   192  	}
   193  	sort.Strings(manifests)
   194  	if glog.V(3) {
   195  		fmt.Printf("Found backup manifest(s): %v\n", manifests)
   196  	}
   197  	return manifests, nil
   198  }
   199  
   200  func (h *fileHandler) ReadManifest(path string, m *Manifest) error {
   201  	return h.readManifest(path, m)
   202  }
   203  
   204  func (h *fileHandler) Close() error {
   205  	if h.fp == nil {
   206  		return nil
   207  	}
   208  	if err := h.fp.Sync(); err != nil {
   209  		glog.Errorf("While closing file: %s. Error: %v", h.fp.Name(), err)
   210  		x.Ignore(h.fp.Close())
   211  		return err
   212  	}
   213  	return h.fp.Close()
   214  }
   215  
   216  func (h *fileHandler) Write(b []byte) (int, error) {
   217  	return h.fp.Write(b)
   218  }
   219  
   220  // pathExist checks if a path (file or dir) is found at target.
   221  // Returns true if found, false otherwise.
   222  func pathExist(path string) bool {
   223  	_, err := os.Stat(path)
   224  	if err == nil {
   225  		return true
   226  	}
   227  	return !os.IsNotExist(err) && !os.IsPermission(err)
   228  }