From b15546820d09fb95fbb5184720d4e4d0bf6961cb Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 7 Aug 2014 22:31:24 -0600 Subject: [PATCH] Add Create() to the backups abstraction. --- state/backups/backups.go | 46 ++++++++++++++++++++++++++ state/backups/backups_test.go | 62 +++++++++++++++++++++++++++++++++++ state/backups/create.go | 11 +++---- state/backups/create_test.go | 2 +- state/backups/db/dump.go | 10 ++++-- state/backups/db/info.go | 12 +++---- state/backups/export_test.go | 37 +++++++++++++++++++-- 7 files changed, 162 insertions(+), 18 deletions(-) diff --git a/state/backups/backups.go b/state/backups/backups.go index 606283d8df4a..bb7edd32a4cd 100644 --- a/state/backups/backups.go +++ b/state/backups/backups.go @@ -6,14 +6,28 @@ package backups import ( + "github.com/juju/errors" "github.com/juju/loggo" "github.com/juju/utils/filestorage" + + "github.com/juju/juju/state/backups/db" + "github.com/juju/juju/state/backups/files" + "github.com/juju/juju/state/backups/metadata" ) var logger = loggo.GetLogger("juju.state.backup") +var ( + getFilesToBackUp = files.GetFilesToBackUp + getDBDumper (func(db.ConnInfo) db.Dumper) = db.NewDumper + runCreate = create +) + // Backups is an abstraction around all juju backup-related functionality. type Backups interface { + // Create creates and stores a new juju backup archive and returns + // its associated metadata. + Create(dbInfo *db.ConnInfo, origin *metadata.Origin, notes string) (*metadata.Metadata, error) } type backups struct { @@ -28,3 +42,35 @@ func NewBackups(stor filestorage.FileStorage) Backups { } return &b } + +// Create creates and stores a new juju backup archive and returns +// its associated metadata. +func (b *backups) Create(dbInfo *db.ConnInfo, origin *metadata.Origin, notes string) (*metadata.Metadata, error) { + // Prep the metadata. + meta := metadata.NewMetadata(*origin, notes, nil) + + // Create the archive. + filesToBackUp, err := getFilesToBackUp("") + if err != nil { + return nil, errors.Annotate(err, "error listing files to back up") + } + dumper := getDBDumper(*dbInfo) + args := createArgs{filesToBackUp, dumper} + result, err := runCreate(&args) + if err != nil { + return nil, errors.Annotate(err, "error creating backup archive") + } + defer result.archiveFile.Close() + + // Store the archive. + err = meta.Finish(result.size, result.checksum, "", nil) + if err != nil { + return nil, errors.Annotate(err, "error updating metadata") + } + _, err = b.storage.Add(meta, result.archiveFile) + if err != nil { + return nil, errors.Annotate(err, "error storing backup archive") + } + + return meta, nil +} diff --git a/state/backups/backups_test.go b/state/backups/backups_test.go index b284e852050d..54d7c469daff 100644 --- a/state/backups/backups_test.go +++ b/state/backups/backups_test.go @@ -4,10 +4,16 @@ package backups_test import ( + "bytes" + "io/ioutil" + + jc "github.com/juju/testing/checkers" "github.com/juju/utils/filestorage" gc "launchpad.net/gocheck" "github.com/juju/juju/state/backups" + "github.com/juju/juju/state/backups/db" + "github.com/juju/juju/state/backups/metadata" "github.com/juju/juju/testing" ) @@ -35,3 +41,59 @@ func (s *backupsSuite) TestNewBackups(c *gc.C) { c.Check(api, gc.NotNil) } + +func (s *backupsSuite) TestCreateOkay(c *gc.C) { + // Patch the internals. + archiveFile := ioutil.NopCloser(bytes.NewBufferString("")) + result := backups.NewTestCreateResult(archiveFile, 10, "") + received, testCreate := backups.NewTestCreate(result, nil) + s.PatchValue(backups.RunCreate, testCreate) + + rootDir := "" + s.PatchValue(backups.GetFilesToBackUp, func(root string) ([]string, error) { + rootDir = root + return []string{""}, nil + }) + + var receivedDBInfo *db.ConnInfo + s.PatchValue(backups.GetDBDumper, func(info db.ConnInfo) db.Dumper { + receivedDBInfo = &info + return nil + }) + + // Run the backup. + dbInfo := db.NewConnInfo("a", "b", "c") + origin := metadata.NewOrigin("", "", "") + meta, err := s.api.Create(dbInfo, origin, "some notes") + + // Test the call values. + filesToBackUp, _ := backups.ExposeCreateArgs(received) + c.Check(filesToBackUp, jc.SameContents, []string{""}) + + address, username, password, err := receivedDBInfo.Check() + c.Assert(err, gc.IsNil) + c.Check(address, gc.Equals, "a") + c.Check(username, gc.Equals, "b") + c.Check(password, gc.Equals, "c") + + c.Check(rootDir, gc.Equals, "") + + // Check the resulting metadata. + c.Check(meta.ID(), gc.Not(gc.Equals), "") + c.Check(meta.Size(), gc.Equals, int64(10)) + c.Check(meta.Checksum(), gc.Equals, "") + c.Check(meta.Stored(), gc.Equals, true) + metaOrigin := meta.Origin() + c.Check(metaOrigin.Environment(), gc.Equals, "") + c.Check(metaOrigin.Machine(), gc.Equals, "") + c.Check(metaOrigin.Hostname(), gc.Equals, "") + c.Check(meta.Notes(), gc.Equals, "some notes") + + // Check the file storage. + storedMeta, storedFile, err := s.storage.Get(meta.ID()) + c.Check(err, gc.IsNil) + c.Check(storedMeta, gc.DeepEquals, meta) + data, err := ioutil.ReadAll(storedFile) + c.Assert(err, gc.IsNil) + c.Check(string(data), gc.Equals, "") +} diff --git a/state/backups/create.go b/state/backups/create.go index f14c700e4290..6fb82a64a3c4 100644 --- a/state/backups/create.go +++ b/state/backups/create.go @@ -16,6 +16,7 @@ import ( "github.com/juju/utils/tar" "github.com/juju/juju/state/backups/archive" + "github.com/juju/juju/state/backups/db" ) // TODO(ericsnow) One concern is files that get out of date by the time @@ -27,13 +28,9 @@ const ( tempFilename = "juju-backup.tar.gz" ) -type dumper interface { - Dump(dumpDir string) error -} - type createArgs struct { filesToBackUp []string - db dumper + db db.Dumper } type createResult struct { @@ -79,7 +76,7 @@ type builder struct { // filesToBackUp is the paths to every file to include in the archive. filesToBackUp []string // db is the wrapper around the DB dump command and args. - db dumper + db db.Dumper // archiveFile is the backup archive file. archiveFile *os.File // bundleFile is the inner archive file containing all the juju @@ -91,7 +88,7 @@ type builder struct { // directories which backup uses as its staging area while building the // archive. It also creates the archive // (temp root, tarball root, DB dumpdir), along with any error. -func newBuilder(filesToBackUp []string, db dumper) (*builder, error) { +func newBuilder(filesToBackUp []string, db db.Dumper) (*builder, error) { b := builder{ filesToBackUp: filesToBackUp, db: db, diff --git a/state/backups/create_test.go b/state/backups/create_test.go index 7f83ec88419b..70371aa0ec24 100644 --- a/state/backups/create_test.go +++ b/state/backups/create_test.go @@ -36,7 +36,7 @@ func (s *createSuite) TestCreateLegacy(c *gc.C) { c.Assert(err, gc.IsNil) c.Assert(result, gc.NotNil) - archiveFile, size, checksum := backups.DumpCreateResult(result) + archiveFile, size, checksum := backups.ExposeCreateResult(result) c.Assert(archiveFile, gc.NotNil) // Check the result. diff --git a/state/backups/db/dump.go b/state/backups/db/dump.go index 882421b6662f..9c374155f147 100644 --- a/state/backups/db/dump.go +++ b/state/backups/db/dump.go @@ -15,6 +15,12 @@ import ( const dumpName = "mongodump" +// Dumper is any type that dumps something to a dump dir. +type Dumper interface { + // Dump something to dumpDir. + Dump(dumpDir string) error +} + var getMongodumpPath = func() (string, error) { mongod, err := mongo.Path() if err != nil { @@ -35,12 +41,12 @@ var getMongodumpPath = func() (string, error) { } type mongoDumper struct { - connInfo + ConnInfo } // NewDumper returns a new value with a Dump method for dumping the // juju state database. -func NewDumper(info connInfo) *mongoDumper { +func NewDumper(info ConnInfo) Dumper { return &mongoDumper{info} } diff --git a/state/backups/db/info.go b/state/backups/db/info.go index 9c0916bebcdb..404f9dcf8f29 100644 --- a/state/backups/db/info.go +++ b/state/backups/db/info.go @@ -10,15 +10,15 @@ import ( ) // ConnInfo is a simplification of authentication.MongoInfo. -type connInfo struct { +type ConnInfo struct { address string username string password string } // NewConnInfo returns a new DB connection info value. -func NewConnInfo(addr, user, pw string) *connInfo { - info := connInfo{ +func NewConnInfo(addr, user, pw string) *ConnInfo { + info := ConnInfo{ address: addr, username: user, password: pw, @@ -28,8 +28,8 @@ func NewConnInfo(addr, user, pw string) *connInfo { // NewMongoConnInfo returns a new DB connection info value based on the // mongo info. -func NewMongoConnInfo(mgoInfo *authentication.MongoInfo) *connInfo { - info := connInfo{ +func NewMongoConnInfo(mgoInfo *authentication.MongoInfo) *ConnInfo { + info := ConnInfo{ address: mgoInfo.Addrs[0], password: mgoInfo.Password, } @@ -43,7 +43,7 @@ func NewMongoConnInfo(mgoInfo *authentication.MongoInfo) *connInfo { } // Check returns the DB connection info, ensuring it is valid. -func (ci *connInfo) Check() (address, username, password string, err error) { +func (ci *ConnInfo) Check() (address, username, password string, err error) { address = ci.address username = ci.username password = ci.password diff --git a/state/backups/export_test.go b/state/backups/export_test.go index 634caa5cc210..8364af9e1fa7 100644 --- a/state/backups/export_test.go +++ b/state/backups/export_test.go @@ -5,17 +5,27 @@ package backups import ( "io" + + "github.com/juju/juju/state/backups/db" ) var ( Create = create + + GetFilesToBackUp = &getFilesToBackUp + GetDBDumper = &getDBDumper + RunCreate = &runCreate ) -func DumpCreateResult(result *createResult) (io.ReadCloser, int64, string) { +func ExposeCreateArgs(args *createArgs) ([]string, db.Dumper) { + return args.filesToBackUp, args.db +} + +func ExposeCreateResult(result *createResult) (io.ReadCloser, int64, string) { return result.archiveFile, result.size, result.checksum } -func NewTestCreateArgs(filesToBackUp []string, db dumper) *createArgs { +func NewTestCreateArgs(filesToBackUp []string, db db.Dumper) *createArgs { args := createArgs{ filesToBackUp: filesToBackUp, db: db, @@ -23,6 +33,15 @@ func NewTestCreateArgs(filesToBackUp []string, db dumper) *createArgs { return &args } +func NewTestCreateResult(file io.ReadCloser, size int64, checksum string) *createResult { + result := createResult{ + archiveFile: file, + size: size, + checksum: checksum, + } + return &result +} + type testDBDumper struct { DumpDir string } @@ -35,3 +54,17 @@ func (d *testDBDumper) Dump(dumpDir string) error { func NewTestDBDumper() *testDBDumper { return &testDBDumper{} } + +func NewTestCreate(result *createResult, err error) (*createArgs, func(*createArgs) (*createResult, error)) { + var received createArgs + + testCreate := func(args *createArgs) (*createResult, error) { + received = *args + if err != nil { + return nil, err + } + return result, nil + } + + return &received, testCreate +}