Commit 4e26c301 authored by Joan Piles's avatar Joan Piles

Took the space checking to a separate gofunc.

It gets called after each successful snapshot iif there is snapshot
retention (i.e. the expired snapshots are not automatically purged).
This should avoid race conditions, since the only two paths to purge()
are (*should be*) mutually exclusive.
parent 585175c3
......@@ -37,20 +37,20 @@ func (o *Opts) Set(value string) error {
// use own struct as "backing store" for parsed flags
type Config struct {
RsyncPath string
RsyncOpts Opts
Origin string
repository string
Schedule string
verbose bool
showAll bool
MaxKeep int
NoPurge bool
NoWait bool
NoLogDate bool
SchedFile string
MinPercSpace float64
MinGiBSpace int
RsyncPath string
RsyncOpts Opts
Origin string
repository string
Schedule string
verbose bool
showAll bool
MaxKeep int
NoPurge bool
NoWait bool
NoLogDate bool
SchedFile string
MinPercSpace float64
MinGiBSpace int
}
// WriteCache writes the global configuration to disk as a json file.
......
......@@ -5,7 +5,7 @@
package main
import (
"log"
"log"
"syscall"
)
......@@ -14,29 +14,29 @@ const GiB = 1024 * 1024 * 1024 // One gibibyte (2^30)
// Function to verify the space constraints specified by the user.
// Return true if all the constraints are satisfied, or in case something unusual happens.
func checkFreeSpace(baseDir string, minPerc float64, minGiB int) bool {
// This is just to avoid the system call if there is nothing to check
if minPerc <= 0 && minGiB <= 0 {
return true
}
var stats syscall.Statfs_t
Debugf("Trying to check free space in %s", baseDir);
err := syscall.Statfs(baseDir, &stats)
if (err != nil) {
log.Println("could not check free space:", err)
return true // We cannot return false if there is an error, otherwise we risk deleting more than we should
}
sizeBytes := uint64(stats.Bsize) * stats.Blocks
freeBytes := uint64(stats.Bsize) * stats.Bfree
Debugf("We have %f GiB, and %f GiB of them are free.", float64(sizeBytes) / GiB, float64(freeBytes) / GiB)
// This is just to avoid the system call if there is nothing to check
if minPerc <= 0 && minGiB <= 0 {
return true
}
var stats syscall.Statfs_t
Debugf("Trying to check free space in %s", baseDir)
err := syscall.Statfs(baseDir, &stats)
if err != nil {
log.Println("could not check free space:", err)
return true // We cannot return false if there is an error, otherwise we risk deleting more than we should
}
sizeBytes := uint64(stats.Bsize) * stats.Blocks
freeBytes := uint64(stats.Bsize) * stats.Bfree
Debugf("We have %f GiB, and %f GiB of them are free.", float64(sizeBytes)/GiB, float64(freeBytes)/GiB)
// The actual check... we fail it we are below either the absolute or the relative value
if int(freeBytes / GiB) < minGiB || (100 * float64(freeBytes) / float64(sizeBytes)) < minPerc {
return false
}
return true
}
\ No newline at end of file
if int(freeBytes/GiB) < minGiB || (100*float64(freeBytes)/float64(sizeBytes)) < minPerc {
return false
}
return true
}
/* See the file "LICENSE.txt" for the full license governing this code. */
package main
import (
"testing"
"testing"
)
const testDir = "/home"
function gatherTestData(baseDir s) data syscall.Statfs_t {
err := syscall.Statfs(testDir, &data)
if (err != nil) {
func gatherTestData(baseDir s) (data syscall.Statfs_t) {
err := syscall.Statfs(testDir, &data)
if err != nil {
log.Println("could not check free space:", err)
}
}
func TestCheckFreeSapce(t *testing.T) {
// First, gather the data
data := gatherTestData("/")
float64 actualFreePerc = float64(data.Bfree) / float64(data.Bytes)
int actualFreeGiB = int(freeBytes / GiB)
// Now, let's make a quick run of the test
result := checkFreeSpace(testDir, 0, 0)
if !result {
t.Errorf("Short run failure")
}
// First, gather the data
data := gatherTestData("/")
var float64 actualFreePerc = float64(data.Bfree) / float64(data.Bytes)
var int actualFreeGiB = int(freeBytes / GiB)
// Now, let's make a quick run of the test
result := checkFreeSpace(testDir, 0, 0)
if !result {
t.Errorf("Short run failure")
}
// Successful absolute free space
result := checkFreeSpace(testDir, 0, actualFreeGiB/2)
if result {
t.Errorf("Error in successful absolute free space test")
}
// Successful absolute free space
result := checkFreeSpace(testDir, 0, actualFreeGiB / 2)
if result {
t.Errorf("Error in successful absolute free space test")
}
// Successful relative free space
result := checkFreeSpace(testDir, actualFreePerc/2, 0)
if result {
t.Errorf("Error in successful absolute free space test")
}
// Successful combined free space
result := checkFreeSpace(testDir, actualFreePerc/2, actualFreeGiB/2)
if result {
t.Errorf("Error in successful combined free space test")
}
// Successful relative free space
result := checkFreeSpace(testDir, actualFreePerc / 2, 0)
if result {
t.Errorf("Error in successful absolute free space test")
}
// Successful combined free space
result := checkFreeSpace(testDir, actualFreePerc / 2, actualFreeGiB / 2)
if result {
t.Errorf("Error in successful combined free space test")
}
// Failed absolute free space
result := checkFreeSpace(testDir, 0, actualFreeGiB * 2)
if result {
t.Errorf("Error in failed absolute free space test")
}
result := checkFreeSpace(testDir, 0, actualFreeGiB*2)
if result {
t.Errorf("Error in failed absolute free space test")
}
// Failed relative free space
result := checkFreeSpace(testDir, actualFreePerc * 2, 0)
if result {
t.Errorf("Error in failed absolute free space test")
}
// Failed combined free space
result := checkFreeSpace(testDir, actualFreePerc * 2, actualFreeGiB * 2)
if result {
t.Errorf("Error in Failed combined free space test")
}
}
\ No newline at end of file
// Failed relative free space
result := checkFreeSpace(testDir, actualFreePerc*2, 0)
if result {
t.Errorf("Error in failed absolute free space test")
}
// Failed combined free space
result := checkFreeSpace(testDir, actualFreePerc*2, actualFreeGiB*2)
if result {
t.Errorf("Error in Failed combined free space test")
}
}
......@@ -79,6 +79,7 @@ func subcmdRun() (ferr error) {
obsoleteQueue := make(chan *Snapshot, 100)
lastGoodIn := make(chan *Snapshot)
lastGoodOut := make(chan *Snapshot)
freeSpaceCheck := make(chan struct{}) // Empty type for the channel: we don't care about what is inside, only about the fact that there is something inside
cl := new(realClock)
go LastGoodTicker(lastGoodIn, lastGoodOut, cl)
......@@ -104,6 +105,12 @@ func subcmdRun() (ferr error) {
lastGoodIn <- sn
Debugf("pruning")
prune(obsoleteQueue, cl)
// If we purge automatically all the expired snapshots,
// there's nothing to remove to free space.
if config.NoPurge {
Debugf("checking space constraints")
freeSpaceCheck <- struct{}{}
}
}
}
}
......@@ -121,13 +128,43 @@ func subcmdRun() (ferr error) {
// Purger loop
go func() {
for {
if sn := <-obsoleteQueue; !config.NoPurge || !checkFreeSpace(config.repository, config.MinPercSpace, config.MinGiBSpace) {
if sn := <-obsoleteQueue; !config.NoPurge {
sn.purge()
}
}
}()
Debugf("started purge goroutine")
// If we are going to automatically purge all expired snapshots, we
// needn't even starting the gofunc
if config.NoPurge {
// Free space claiming function
go func() {
for {
<-freeSpaceCheck // Wait until we are ordered to do something
// Get all obsolete snapshots
snapshots, err := FindSnapshots(cl) // This returns a sorted list
if err != nil {
log.Println(err)
return
}
if len(snapshots) < 2 {
log.Println("less than 2 snapshots found, not pruning")
return
}
obsolete := snapshots.state(STATE_OBSOLETE, 0)
// We only delete as long as we need *AND* we have something to delete
for !checkFreeSpace(config.repository, config.MinPercSpace, config.MinGiBSpace) && len(obsolete) > 0 {
// If there is not enough space, purge the oldest snapshot
last := len(obsolete) - 1
obsolete[last].purge()
// We remove it from the list, it's quicker than recalculating the list.
obsolete = obsolete[:(last - 1)]
}
}
}()
}
// Global signal handling
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
......
......@@ -15,7 +15,7 @@ import (
func prune(q chan *Snapshot, cl Clock) {
intervals := schedules[config.Schedule]
// interval 0 does not need pruning, start with 1
for i := len(intervals)-2; i > 0; i-- {
for i := len(intervals) - 2; i > 0; i-- {
snapshots, err := FindSnapshots(cl)
if err != nil {
log.Println(err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment