Commit 585175c3 authored by Joan Piles's avatar Joan Piles

First version of space checks. *STILL NOT FULLY FUNCTIONAL*

It adds two configuration knobs and tries to prune snapshots from oldest
to newest (thus in case of low space snapshots will be deleted until
needed).

We have to account for the case were a snapshot was previously
obsoleted, but new space constraints make us have to delete it (perhaps
check for even older snapshots when low disk is detected?)
parent dfb91642
......@@ -37,18 +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
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.
......@@ -91,6 +93,8 @@ func (c *Config) ReadCache() error {
c.Schedule = t.Schedule
c.MaxKeep = t.MaxKeep
c.NoPurge = t.NoPurge
c.MinPercSpace = t.MinPercSpace
c.MinGiBSpace = t.MinGiBSpace
}
return nil
}
......@@ -142,7 +146,7 @@ func LoadConfig() *Config {
"how many snapshots to keep in highest (oldest) interval. Use 0 to keep all")
flags.BoolVar(&(config.NoPurge),
"noPurge", false,
"if set, obsolete snapshots will not be deleted")
"if set, obsolete snapshots will not be deleted (minimum space requirements will still be honoured)")
flags.BoolVar(&(config.NoWait),
"noWait", false,
"if set, skip the initial waiting time before the first snapshot")
......@@ -152,6 +156,13 @@ func LoadConfig() *Config {
flags.StringVar(&(config.SchedFile),
"schedFile", defaultSchedFileName,
"path to external schedules")
flags.Float64Var(&(config.MinPercSpace),
"minPercSpace", 0,
"if set, keep at least x% of the snapshots filesystem free")
flags.IntVar(&(config.MinGiBSpace),
"minGbSpace", 0,
"if set, keep at least x GiB of the snapshots filesystem free")
flags.Parse(os.Args[2:])
if config.SchedFile != "" {
schedules.AddFromFile(config.SchedFile)
......
/* See the file "LICENSE.txt" for the full license governing this code. */
// Low-level filesystem utilities
package main
import (
"log"
"syscall"
)
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)
// 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
package main
import (
"testing"
)
const testDir = "/home"
function 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")
}
// 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")
}
// Failed absolute free space
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
......@@ -121,7 +121,7 @@ func subcmdRun() (ferr error) {
// Purger loop
go func() {
for {
if sn := <-obsoleteQueue; !config.NoPurge {
if sn := <-obsoleteQueue; !config.NoPurge || !checkFreeSpace(config.repository, config.MinPercSpace, config.MinGiBSpace) {
sn.purge()
}
}
......
......@@ -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 := 1; i < len(intervals)-1; 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