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 }