github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/backups/files.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package backups
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  
    11  	"github.com/juju/errors"
    12  
    13  	"github.com/juju/juju/mongo"
    14  )
    15  
    16  // TODO(ericsnow) lp-1392876
    17  // Pull these from authoritative sources (see
    18  // github.com/juju/juju/juju/paths, etc.):
    19  const (
    20  	dataDir = "/var/lib/juju"
    21  	logsDir = "/var/log/juju"
    22  	sshDir  = "/home/ubuntu/.ssh"
    23  
    24  	agentsDir   = "agents"
    25  	agentsConfs = "machine-*"
    26  	toolsDir    = "tools"
    27  	initDir     = "init"
    28  
    29  	sshIdentFile = "system-identity"
    30  	nonceFile    = "nonce.txt"
    31  	authKeysFile = "authorized_keys"
    32  
    33  	dbPEM    = "server.pem"
    34  	dbSecret = "shared-secret"
    35  )
    36  
    37  // Paths holds the paths that backups needs.
    38  type Paths struct {
    39  	DataDir string
    40  	LogsDir string
    41  }
    42  
    43  // GetFilesToBackUp returns the paths that should be included in the
    44  // backup archive.
    45  func GetFilesToBackUp(rootDir string, paths *Paths, oldmachine string) ([]string, error) {
    46  	var glob string
    47  
    48  	glob = filepath.Join(rootDir, paths.DataDir, agentsDir, agentsConfs)
    49  	agentConfs, err := filepath.Glob(glob)
    50  	if err != nil {
    51  		return nil, errors.Annotate(err, "failed to fetch agent config files")
    52  	}
    53  
    54  	glob = filepath.Join(rootDir, paths.DataDir, initDir, "*")
    55  	serviceConfs, err := filepath.Glob(glob)
    56  	if err != nil {
    57  		return nil, errors.Annotate(err, "failed to fetch service config files")
    58  	}
    59  
    60  	backupFiles := []string{
    61  		filepath.Join(rootDir, paths.DataDir, toolsDir),
    62  
    63  		filepath.Join(rootDir, paths.DataDir, sshIdentFile),
    64  
    65  		filepath.Join(rootDir, paths.DataDir, dbPEM),
    66  		filepath.Join(rootDir, paths.DataDir, dbSecret),
    67  	}
    68  	backupFiles = append(backupFiles, agentConfs...)
    69  	backupFiles = append(backupFiles, serviceConfs...)
    70  
    71  	// Handle nonce.txt (might not exist).
    72  	nonce := filepath.Join(rootDir, paths.DataDir, nonceFile)
    73  	if _, err := os.Stat(nonce); err != nil {
    74  		if !os.IsNotExist(err) {
    75  			return nil, errors.Trace(err)
    76  		}
    77  		logger.Errorf("skipping missing file %q", nonce)
    78  	} else {
    79  		backupFiles = append(backupFiles, nonce)
    80  	}
    81  
    82  	// Handle user SSH files (might not exist).
    83  	SSHDir := filepath.Join(rootDir, sshDir)
    84  	if _, err := os.Stat(SSHDir); err != nil {
    85  		if !os.IsNotExist(err) {
    86  			return nil, errors.Trace(err)
    87  		}
    88  		logger.Errorf("skipping missing dir %q", SSHDir)
    89  	} else {
    90  		backupFiles = append(backupFiles, filepath.Join(SSHDir, authKeysFile))
    91  	}
    92  
    93  	return backupFiles, nil
    94  }
    95  
    96  // replaceableFolders for testing purposes.
    97  var replaceableFolders = replaceableFoldersFunc
    98  
    99  // replaceableFoldersFunc will return a map with the folders that need to
   100  // be replaced so they can be deleted prior to a restore.
   101  // Mongo 2.4 requires that the database directory be removed, while
   102  // Mongo 3.2 requires that it not be removed
   103  func replaceableFoldersFunc(dataDir string, mongoVersion mongo.Version) (map[string]os.FileMode, error) {
   104  	replaceables := map[string]os.FileMode{}
   105  
   106  	// NOTE: never put dataDir in here directly as that will unconditionally
   107  	// remove the database.
   108  	dirs := []string{
   109  		filepath.Join(dataDir, "init"),
   110  		filepath.Join(dataDir, "tools"),
   111  		filepath.Join(dataDir, "agents"),
   112  	}
   113  	if mongoVersion.Major == 2 {
   114  		dirs = append(dirs, filepath.Join(dataDir, "db"))
   115  	}
   116  
   117  	for _, replaceable := range dirs {
   118  		dirStat, err := os.Stat(replaceable)
   119  		if os.IsNotExist(err) {
   120  			continue
   121  		}
   122  		if err != nil {
   123  			return map[string]os.FileMode{}, errors.Annotatef(err, "cannot stat %q", replaceable)
   124  		}
   125  		replaceables[replaceable] = dirStat.Mode()
   126  	}
   127  	return replaceables, nil
   128  }
   129  
   130  // TODO (perrito666) make this version sensitive when these files change, it would
   131  // also be a good idea to save these instead of deleting them.
   132  
   133  // PrepareMachineForRestore deletes all files from the re-bootstrapped
   134  // machine that are to be replaced by the backup and recreates those
   135  // directories that are to contain new files; this is to avoid
   136  // possible mixup from new/old files that lead to an inconsistent
   137  // restored state machine.
   138  func PrepareMachineForRestore(mongoVersion mongo.Version) error {
   139  	replaceFolders, err := replaceableFolders(dataDir, mongoVersion)
   140  	if err != nil {
   141  		return errors.Annotate(err, "cannot retrieve the list of folders to be cleaned before restore")
   142  	}
   143  	var keys []string
   144  	for k := range replaceFolders {
   145  		keys = append(keys, k)
   146  	}
   147  	// sort to avoid trying to create subfolders before folders.
   148  	sort.Strings(keys)
   149  	for _, toBeRecreated := range keys {
   150  		fmode := replaceFolders[toBeRecreated]
   151  		_, err := os.Stat(toBeRecreated)
   152  		if err != nil && !os.IsNotExist(err) {
   153  			return errors.Trace(err)
   154  		}
   155  		if !fmode.IsDir() {
   156  			continue
   157  		}
   158  		logger.Debugf("removing dir: %s", toBeRecreated)
   159  		if err := os.RemoveAll(toBeRecreated); err != nil {
   160  			return errors.Trace(err)
   161  		}
   162  		if err := os.MkdirAll(toBeRecreated, fmode); err != nil {
   163  			return errors.Trace(err)
   164  		}
   165  	}
   166  	return nil
   167  }