1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
15
"github.com/juju/errors"
16
"github.com/juju/loggo"
17
"github.com/juju/utils/hash"
18
"github.com/juju/utils/tar"
21
// TODO(ericsnow) One concern is files that get out of date by the time
22
// backup finishes running. This is particularly a problem with log
26
tempPrefix = "jujuBackup-"
27
tempFilename = "juju-backup.tar.gz"
30
type createArgs struct {
31
filesToBackUp []string
33
metadataReader io.Reader
36
type createResult struct {
37
archiveFile io.ReadCloser
42
// create builds a new backup archive file and returns it. It also
43
// updates the metadata with the file info.
44
func create(args *createArgs) (_ *createResult, err error) {
45
// Prepare the backup builder.
46
builder, err := newBuilder(args.filesToBackUp, args.db)
48
return nil, errors.Trace(err)
51
if cerr := builder.cleanUp(); cerr != nil {
59
// Inject the metadata file.
60
if args.metadataReader == nil {
61
return nil, errors.New("missing metadataReader")
63
if err := builder.injectMetadataFile(args.metadataReader); err != nil {
64
return nil, errors.Trace(err)
68
if err := builder.buildAll(); err != nil {
69
return nil, errors.Trace(err)
73
result, err := builder.result()
75
return nil, errors.Trace(err)
78
// Return the result. Note that the entire build workspace will be
79
// deleted at the end of this function. This includes the backup
80
// archive file we built. However, the handle to that file in the
81
// result will still be open and readable.
82
// If we ever support state machines on Windows, this will need to
83
// change (you can't delete open files on Windows).
87
// builder exposes the machinery for creating a backup of juju's state.
89
// rootDir is the root of the archive workspace.
91
// archivePaths is the backups archive summary.
92
archivePaths ArchivePaths
93
// filename is the path to the archive file.
95
// filesToBackUp is the paths to every file to include in the archive.
96
filesToBackUp []string
97
// db is the wrapper around the DB dump command and args.
99
// checksum is the checksum of the archive file.
101
// archiveFile is the backup archive file.
102
archiveFile io.WriteCloser
103
// bundleFile is the inner archive file containing all the juju
104
// state-related files gathered during backup.
105
bundleFile io.WriteCloser
108
// newBuilder returns a new backup archive builder. It creates the temp
109
// directories which backup uses as its staging area while building the
110
// archive. It also creates the archive
111
// (temp root, tarball root, DB dumpdir), along with any error.
112
func newBuilder(filesToBackUp []string, db DBDumper) (b *builder, err error) {
113
// Create the backups workspace root directory.
114
rootDir, err := ioutil.TempDir("", tempPrefix)
116
return nil, errors.Annotate(err, "while making backups workspace")
119
// Populate the builder.
122
archivePaths: NewNonCanonicalArchivePaths(rootDir),
123
filename: filepath.Join(rootDir, tempFilename),
124
filesToBackUp: filesToBackUp,
129
if cerr := b.cleanUp(); cerr != nil {
135
// Create all the direcories we need. We go with user-only
136
// permissions on principle; the directories are short-lived so in
137
// practice it shouldn't matter much.
138
err = os.MkdirAll(b.archivePaths.DBDumpDir, 0700)
140
return nil, errors.Annotate(err, "while creating temp directories")
143
// Create the archive files. We do so here to fail as early as
145
b.archiveFile, err = os.Create(b.filename)
147
return nil, errors.Annotate(err, "while creating archive file")
150
b.bundleFile, err = os.Create(b.archivePaths.FilesBundle)
152
return nil, errors.Annotate(err, `while creating bundle file`)
158
func (b *builder) closeArchiveFile() error {
159
// Currently this method isn't thread-safe (doesn't need to be).
160
if b.archiveFile == nil {
164
if err := b.archiveFile.Close(); err != nil {
165
return errors.Annotate(err, "while closing archive file")
172
func (b *builder) closeBundleFile() error {
173
// Currently this method isn't thread-safe (doesn't need to be).
174
if b.bundleFile == nil {
178
if err := b.bundleFile.Close(); err != nil {
179
return errors.Annotate(err, "while closing bundle file")
186
func (b *builder) removeRootDir() error {
187
// Currently this method isn't thread-safe (doesn't need to be).
189
panic("rootDir is unexpected empty")
192
if err := os.RemoveAll(b.rootDir); err != nil {
193
return errors.Annotate(err, "while removing backups temp dir")
199
type cleanupErrors struct {
203
func (e cleanupErrors) Error() string {
204
if len(e.Errors) == 1 {
205
return fmt.Sprintf("while cleaning up: %v", e.Errors[0])
207
return fmt.Sprintf("%d errors during cleanup", len(e.Errors))
211
func (e cleanupErrors) Log(logger loggo.Logger) {
212
logger.Errorf(e.Error())
213
for _, err := range e.Errors {
214
logger.Errorf(err.Error())
218
func (b *builder) cleanUp() *cleanupErrors {
221
if err := b.closeBundleFile(); err != nil {
222
errors = append(errors, err)
224
if err := b.closeArchiveFile(); err != nil {
225
errors = append(errors, err)
227
if err := b.removeRootDir(); err != nil {
228
errors = append(errors, err)
232
return &cleanupErrors{errors}
237
func (b *builder) injectMetadataFile(source io.Reader) error {
238
err := writeAll(b.archivePaths.MetadataFile, source)
239
return errors.Trace(err)
242
func writeAll(targetname string, source io.Reader) error {
243
target, err := os.Create(targetname)
245
return errors.Annotatef(err, "while creating file %q", targetname)
247
_, err = io.Copy(target, source)
250
return errors.Annotatef(err, "while copying into file %q", targetname)
252
return errors.Trace(target.Close())
255
func (b *builder) buildFilesBundle() error {
256
logger.Infof("dumping juju state-related files")
257
if len(b.filesToBackUp) == 0 {
258
return errors.New("missing list of files to back up")
260
if b.bundleFile == nil {
261
return errors.New("missing bundleFile")
264
stripPrefix := string(os.PathSeparator)
265
_, err := tar.TarFiles(b.filesToBackUp, b.bundleFile, stripPrefix)
267
return errors.Annotate(err, "while bundling state-critical files")
273
func (b *builder) buildDBDump() error {
274
logger.Infof("dumping database")
276
logger.Infof("nothing to do")
280
dumpDir := b.archivePaths.DBDumpDir
281
if err := b.db.Dump(dumpDir); err != nil {
282
return errors.Annotate(err, "while dumping juju state database")
288
func (b *builder) buildArchive(outFile io.Writer) error {
289
tarball := gzip.NewWriter(outFile)
290
defer tarball.Close()
292
// We add a trailing slash (or whatever) to root so that everything
293
// in the path up to and including that slash is stripped off when
294
// each file is added to the tar file.
295
stripPrefix := b.rootDir + string(os.PathSeparator)
296
filenames := []string{b.archivePaths.ContentDir}
297
if _, err := tar.TarFiles(filenames, tarball, stripPrefix); err != nil {
298
return errors.Annotate(err, "while bundling final archive")
304
func (b *builder) buildArchiveAndChecksum() error {
305
if b.archiveFile == nil {
306
return errors.New("missing archiveFile")
308
logger.Infof("building archive file %q", b.filename)
310
// Build the tarball, writing out to both the archive file and a
311
// SHA1 hash. The hash will correspond to the gzipped file rather
312
// than to the uncompressed contents of the tarball. This is so
313
// that users can compare the published checksum against the
314
// checksum of the file without having to decompress it first.
315
hasher := hash.NewHashingWriter(b.archiveFile, sha1.New())
316
if err := b.buildArchive(hasher); err != nil {
317
return errors.Trace(err)
320
// Save the SHA1 checksum.
321
// Gzip writers may buffer what they're writing so we must call
322
// Close() on the writer *before* getting the checksum from the
324
b.checksum = hasher.Base64Sum()
329
func (b *builder) buildAll() error {
331
if err := b.buildFilesBundle(); err != nil {
332
return errors.Trace(err)
335
// Dump the database.
336
if err := b.buildDBDump(); err != nil {
337
return errors.Trace(err)
340
// Bundle it all into a tarball.
341
if err := b.buildArchiveAndChecksum(); err != nil {
342
return errors.Trace(err)
348
// result returns a "create" result relative to the current state of the
349
// builder. create() uses this method to get the final backup result
350
// from the builder it used.
352
// Note that create() calls builder.cleanUp() after it calls
353
// builder.result(). cleanUp() causes the builder's workspace directory
354
// to be deleted. This means that while the file in the result is still
355
// open, it no longer corresponds to any filename on the filesystem.
356
// We do this to avoid leaving any temporary files around. The
357
// consequence is that we cannot simply return the temp filename, we
358
// must leave the file open, and the caller is responsible for closing
359
// the file (hence io.ReadCloser).
360
func (b *builder) result() (*createResult, error) {
361
// Open the file in read-only mode.
362
file, err := os.Open(b.filename)
364
return nil, errors.Annotate(err, "while opening archive file")
368
stat, err := file.Stat()
370
if err := file.Close(); err != nil {
371
// We don't want to just throw the error away.
372
err = errors.Annotate(err, "while closing file during handling of another error")
373
logger.Errorf(err.Error())
375
return nil, errors.Annotate(err, "while reading archive file info")
380
checksum := b.checksum
382
// Return the result.
383
result := createResult{