diff --git a/internal/env/backup.go b/internal/env/backup.go index a4b8b28..6caaffd 100644 --- a/internal/env/backup.go +++ b/internal/env/backup.go @@ -12,6 +12,7 @@ import ( "time" "github.com/FAU-CDI/wisski-distillery/internal/core" + "github.com/FAU-CDI/wisski-distillery/pkg/countwriter" "github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/pkg/errors" @@ -58,45 +59,48 @@ func (backup Backup) String() string { } // Report writes a report from backup into w -func (backup Backup) Report(w io.Writer) { - // TODO: Errors - encoder := json.NewEncoder(w) +func (backup Backup) Report(w io.Writer) (int, error) { + cw := countwriter.NewCountWriter(w) + + encoder := json.NewEncoder(cw) encoder.SetIndent("", " ") - io.WriteString(w, "======= Backup =======\n") + io.WriteString(cw, "======= Backup =======\n") - fmt.Fprintf(w, "Start: %s\n", backup.StartTime) - fmt.Fprintf(w, "End: %s\n", backup.EndTime) - io.WriteString(w, "\n") + fmt.Fprintf(cw, "Start: %s\n", backup.StartTime) + fmt.Fprintf(cw, "End: %s\n", backup.EndTime) + io.WriteString(cw, "\n") - io.WriteString(w, "======= Description =======\n") + io.WriteString(cw, "======= Description =======\n") encoder.Encode(backup.Description) - io.WriteString(w, "\n") + io.WriteString(cw, "\n") - io.WriteString(w, "======= Errors =======\n") - fmt.Fprintf(w, "Panic: %v\n", backup.ErrPanic) - fmt.Fprintf(w, "SQLErr: %s\n", backup.SQLErr) - fmt.Fprintf(w, "TSErr: %s\n", backup.TSErr) - fmt.Fprintf(w, "ConfigFileErr: %s\n", backup.ConfigFileErr) - fmt.Fprintf(w, "InstanceListErr: %s\n", backup.InstanceListErr) + io.WriteString(cw, "======= Errors =======\n") + fmt.Fprintf(cw, "Panic: %v\n", backup.ErrPanic) + fmt.Fprintf(cw, "SQLErr: %s\n", backup.SQLErr) + fmt.Fprintf(cw, "TSErr: %s\n", backup.TSErr) + fmt.Fprintf(cw, "ConfigFileErr: %s\n", backup.ConfigFileErr) + fmt.Fprintf(cw, "InstanceListErr: %s\n", backup.InstanceListErr) - io.WriteString(w, "\n") + io.WriteString(cw, "\n") - io.WriteString(w, "======= Config Files =======\n") + io.WriteString(cw, "======= Config Files =======\n") encoder.Encode(backup.ConfigFilesManifest) // TODO: Proper manifest - io.WriteString(w, "======= Snapshots =======\n") + io.WriteString(cw, "======= Snapshots =======\n") for _, s := range backup.InstanceSnapshots { - io.WriteString(w, s.String()) - io.WriteString(w, "\n") + io.WriteString(cw, s.String()) + io.WriteString(cw, "\n") } - io.WriteString(w, "======= Manifest =======\n") + io.WriteString(cw, "======= Manifest =======\n") for _, file := range backup.Manifest { - io.WriteString(w, file+"\n") + io.WriteString(cw, file+"\n") } - io.WriteString(w, "\n") + io.WriteString(cw, "\n") + + return cw.Sum() } func (dis *Distillery) Backup(io stream.IOStream, description BackupDescription) (backup Backup) { diff --git a/internal/env/snapshot.go b/internal/env/snapshot.go index cb5ffc7..b160964 100644 --- a/internal/env/snapshot.go +++ b/internal/env/snapshot.go @@ -12,6 +12,7 @@ import ( "time" "github.com/FAU-CDI/wisski-distillery/pkg/bookkeeping" + "github.com/FAU-CDI/wisski-distillery/pkg/countwriter" "github.com/FAU-CDI/wisski-distillery/pkg/fsx" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/password" @@ -112,47 +113,51 @@ func (snapshot Snapshot) String() string { } // Report writes a report from snapshot into w -func (snapshot Snapshot) Report(w io.Writer) { +func (snapshot Snapshot) Report(w io.Writer) (int, error) { + ww := countwriter.NewCountWriter(w) + // TODO: Errors of the writer! - encoder := json.NewEncoder(w) + encoder := json.NewEncoder(ww) encoder.SetIndent("", " ") - io.WriteString(w, "======= Begin Snapshot Report "+snapshot.Instance.Slug+" =======\n") + io.WriteString(ww, "======= Begin Snapshot Report "+snapshot.Instance.Slug+" =======\n") - fmt.Fprintf(w, "Slug: %s\n", snapshot.Instance.Slug) - fmt.Fprintf(w, "Dest: %s\n", snapshot.Description.Dest) + fmt.Fprintf(ww, "Slug: %s\n", snapshot.Instance.Slug) + fmt.Fprintf(ww, "Dest: %s\n", snapshot.Description.Dest) - fmt.Fprintf(w, "Start: %s\n", snapshot.StartTime) - fmt.Fprintf(w, "End: %s\n", snapshot.EndTime) - io.WriteString(w, "\n") + fmt.Fprintf(ww, "Start: %s\n", snapshot.StartTime) + fmt.Fprintf(ww, "End: %s\n", snapshot.EndTime) + io.WriteString(ww, "\n") - io.WriteString(w, "======= Description =======\n") + io.WriteString(ww, "======= Description =======\n") encoder.Encode(snapshot.Description) - io.WriteString(w, "\n") + io.WriteString(ww, "\n") - io.WriteString(w, "======= Instance =======\n") + io.WriteString(ww, "======= Instance =======\n") encoder.Encode(snapshot.Instance) - io.WriteString(w, "\n") + io.WriteString(ww, "\n") - io.WriteString(w, "======= Errors =======\n") - fmt.Fprintf(w, "Panic: %v\n", snapshot.ErrPanic) - fmt.Fprintf(w, "Start: %s\n", snapshot.ErrStart) - fmt.Fprintf(w, "Stop: %s\n", snapshot.ErrStop) - fmt.Fprintf(w, "Bookkeep: %s\n", snapshot.ErrBookkeep) - fmt.Fprintf(w, "Pathbuilder: %s\n", snapshot.ErrPathbuilder) - fmt.Fprintf(w, "Filesystem: %s\n", snapshot.ErrFilesystem) - fmt.Fprintf(w, "Triplestore: %s\n", snapshot.ErrTriplestore) - fmt.Fprintf(w, "SQL: %s\n", snapshot.ErrSQL) - io.WriteString(w, "\n") + io.WriteString(ww, "======= Errors =======\n") + fmt.Fprintf(ww, "Panic: %v\n", snapshot.ErrPanic) + fmt.Fprintf(ww, "Start: %s\n", snapshot.ErrStart) + fmt.Fprintf(ww, "Stop: %s\n", snapshot.ErrStop) + fmt.Fprintf(ww, "Bookkeep: %s\n", snapshot.ErrBookkeep) + fmt.Fprintf(ww, "Pathbuilder: %s\n", snapshot.ErrPathbuilder) + fmt.Fprintf(ww, "Filesystem: %s\n", snapshot.ErrFilesystem) + fmt.Fprintf(ww, "Triplestore: %s\n", snapshot.ErrTriplestore) + fmt.Fprintf(ww, "SQL: %s\n", snapshot.ErrSQL) + io.WriteString(ww, "\n") - io.WriteString(w, "======= Manifest =======\n") + io.WriteString(ww, "======= Manifest =======\n") for _, file := range snapshot.Manifest { - io.WriteString(w, file+"\n") + io.WriteString(ww, file+"\n") } - io.WriteString(w, "\n") + io.WriteString(ww, "\n") - io.WriteString(w, "======= End Snapshot Report "+snapshot.Instance.Slug+" =======\n") + io.WriteString(ww, "======= End Snapshot Report "+snapshot.Instance.Slug+"=======\n") + + return ww.Sum() } // Snapshot creates a new snapshot of this instance into dest @@ -169,7 +174,7 @@ func (instance Instance) Snapshot(io stream.IOStream, desc SnapshotDescription) // do the create keeping track of time! logging.LogOperation(func() error { snapshot.StartTime = time.Now() - snapshot.create(io, instance) + snapshot.makeBlackbox(io, instance) snapshot.EndTime = time.Now() return nil @@ -178,7 +183,9 @@ func (instance Instance) Snapshot(io stream.IOStream, desc SnapshotDescription) return } -func (snapshot *Snapshot) create(io stream.IOStream, instance Instance) { +// mainBlackbox runs the blackbox backup of the system. +// It pauses the Instance, if a consistent state is required. +func (snapshot *Snapshot) makeBlackbox(io stream.IOStream, instance Instance) { stack := instance.Stack() // stop the instance (unless it was explicitly asked to not do so!) diff --git a/pkg/countwriter/writer.go b/pkg/countwriter/writer.go new file mode 100644 index 0000000..00a8c49 --- /dev/null +++ b/pkg/countwriter/writer.go @@ -0,0 +1,62 @@ +package countwriter + +import ( + "io" +) + +// CountWriter wraps an io.Writer, see [NewCountWriter]. +// +// It is intended to be used to count different writes to an underlying writer. +// Once an error occurs, no more writes are passed through, and the underlying error is returned instead. +// This means that in practice, calls to write can be continued and are ignored silently. +// +// The underlying sum of bytes written and error can be seen using [Sum]. +type CountWriter struct { + w io.Writer + + n int + err error +} + +// NewCountWriter creates a new [CountWriter] that delegates to w. +func NewCountWriter(w io.Writer) *CountWriter { + return &CountWriter{w: w} +} + +// write performs the write operation w on this writer. +func (cw *CountWriter) write(w func() (int, error)) (int, error) { + // if there was an error, return it and don't do a write + if cw.err != nil { + return 0, cw.err + } + + // call the writer + n, err := w() + + // update the underling state + cw.n += n + cw.err = err + + // and return + return n, err +} + +// Write implements [io.Writer] +func (cw *CountWriter) Write(p []byte) (int, error) { + return cw.write(func() (int, error) { + return cw.w.Write(p) + }) +} + +// WriteString implements [io.WriteString]. +// See [Write]. +func (cw *CountWriter) WriteString(s string) (int, error) { + return cw.write(func() (int, error) { + return io.WriteString(cw.w, s) + }) +} + +// Sum returns the state, that is the total number of bytes written and any error +func (cw *CountWriter) Sum() (int, error) { + return cw.n, cw.err +}