Files
mgmt/resources/refresh.go
James Shubin 2e718c0e9d resources: Improve notification system and notify refreshes
Resources can send "refresh" notifications along edges. These messages
are sent whenever the upstream (initiating vertex) changes state. When
the changed state propagates downstream, it will be paired with a
refresh flag which can be queried in the CheckApply method of that
resource.

Future work will include a stateful refresh tracking mechanism so that
if a refresh event is generated and not consumed, it will be saved
across an interrupt (shutdown) or a crash so that it can be re-applied
on the subsequent run. This is important because the unapplied refresh
is a form of hysteresis which needs to be tracked and remembered or we
won't be able to determine that the state is wrong!

Still to do:
* Update the autogrouping code to handle the edge notify properties!
* Actually finish the stateful bool code
2016-12-03 01:35:31 -05:00

105 lines
3.4 KiB
Go

// Mgmt
// Copyright (C) 2013-2016+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"fmt"
"os"
"strings"
errwrap "github.com/pkg/errors"
)
// Refresh returns the pending state of a notification. It should only be called
// in the CheckApply portion of a resource where a refresh should be acted upon.
func (obj *BaseRes) Refresh() bool {
return obj.refresh
}
// SetRefresh sets the pending state of a notification. It should only be called
// by the mgmt engine.
func (obj *BaseRes) SetRefresh(b bool) {
obj.refresh = b
}
// StatefulBool is an interface for storing a boolean flag in a permanent spot.
type StatefulBool interface {
Get() (bool, error) // get value of token
Set() error // set token to true
Del() error // rm token if it exists
}
// DiskBool stores a boolean variable on disk for stateful access across runs.
// The absence of the path is treated as false. If the path contains a special
// value, then it is treated as true. All the other non-error cases are false.
type DiskBool struct {
Path string // path to token
}
// str returns the string data which represents true (aka set).
func (obj *DiskBool) str() string {
const TrueToken = "true"
const newline = "\n"
return TrueToken + newline
}
// Get returns if the boolean setting, if no error reading the value occurs.
func (obj *DiskBool) Get() (bool, error) {
file, err := os.Open(obj.Path) // open a handle to read the file
if err != nil {
if os.IsNotExist(err) {
return false, nil // no token means value is false
}
return false, errwrap.Wrapf(err, "could not read token")
}
defer file.Close()
str := obj.str()
data := make([]byte, len(str)) // data + newline
if _, err := file.Read(data); err != nil {
return false, errwrap.Wrapf(err, "could not read from file")
}
return strings.TrimSpace(string(data)) == strings.TrimSpace(str), nil
}
// Set stores the true boolean value, if no error setting the value occurs.
func (obj *DiskBool) Set() error {
file, err := os.Create(obj.Path) // open a handle to create the file
if err != nil {
return errwrap.Wrapf(err, "can't create file")
}
defer file.Close()
str := obj.str()
if c, err := file.Write([]byte(str)); err != nil {
return errwrap.Wrapf(err, "error writing to file")
} else if l := len(str); c != l {
return fmt.Errorf("wrote %d bytes instead of %d", c, l)
}
return file.Sync() // guarantee it!
}
// Del stores the false boolean value, if no error clearing the value occurs.
func (obj *DiskBool) Del() error {
if err := os.Remove(obj.Path); err != nil { // remove the file
if os.IsNotExist(err) {
return nil // no file means this is already fine
}
return errwrap.Wrapf(err, "could not delete token")
}
return nil
}