Skip to content

Commit

Permalink
Add Create() to the backups abstraction.
Browse files Browse the repository at this point in the history
  • Loading branch information
ericsnowcurrently committed Aug 26, 2014
1 parent 3ef634e commit b155468
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 18 deletions.
46 changes: 46 additions & 0 deletions state/backups/backups.go
Expand Up @@ -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 {
Expand All @@ -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
}
62 changes: 62 additions & 0 deletions state/backups/backups_test.go
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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("<compressed tarball>"))
result := backups.NewTestCreateResult(archiveFile, 10, "<checksum>")
received, testCreate := backups.NewTestCreate(result, nil)
s.PatchValue(backups.RunCreate, testCreate)

rootDir := "<was never set>"
s.PatchValue(backups.GetFilesToBackUp, func(root string) ([]string, error) {
rootDir = root
return []string{"<some file>"}, 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("<env ID>", "<machine ID>", "<hostname>")
meta, err := s.api.Create(dbInfo, origin, "some notes")

// Test the call values.
filesToBackUp, _ := backups.ExposeCreateArgs(received)
c.Check(filesToBackUp, jc.SameContents, []string{"<some file>"})

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, "<checksum>")
c.Check(meta.Stored(), gc.Equals, true)
metaOrigin := meta.Origin()
c.Check(metaOrigin.Environment(), gc.Equals, "<env ID>")
c.Check(metaOrigin.Machine(), gc.Equals, "<machine ID>")
c.Check(metaOrigin.Hostname(), gc.Equals, "<hostname>")
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, "<compressed tarball>")
}
11 changes: 4 additions & 7 deletions state/backups/create.go
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion state/backups/create_test.go
Expand Up @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions state/backups/db/dump.go
Expand Up @@ -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 {
Expand All @@ -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}
}

Expand Down
12 changes: 6 additions & 6 deletions state/backups/db/info.go
Expand Up @@ -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,
Expand All @@ -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,
}
Expand All @@ -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
Expand Down
37 changes: 35 additions & 2 deletions state/backups/export_test.go
Expand Up @@ -5,24 +5,43 @@ 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,
}
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
}
Expand All @@ -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
}

0 comments on commit b155468

Please sign in to comment.