Skip to content
Snippets Groups Projects
change.go 3.41 KiB
Newer Older
  • Learn to ignore specific revisions
  • package pnd
    
    import (
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"errors"
    
    	"github.com/google/uuid"
    	"github.com/openconfig/ygot/ygot"
    	log "github.com/sirupsen/logrus"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"golang.org/x/net/context"
    	"os"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    var changeTimeout time.Duration
    
    func init() {
    
    	var err error
    	e := os.Getenv("GOSDN_CHANGE_TIMEOUT")
    	if e != "" {
    		changeTimeout, err = time.ParseDuration(e)
    		if err != nil {
    			log.Fatal(err)
    		}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	} else {
    		changeTimeout, err = time.ParseDuration("10m")
    		if err != nil {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		}
    	}
    	log.Debugf("change timeout set to %v", changeTimeout)
    }
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // NewChange takes a Device UUID, a pair GoStructs (current and intended state)
    // a callback function and a channel for errors and returns a *Change
    // The callback function is used by the Commit() and Confirm() functions. It
    // must define how the change is carried out.
    
    func NewChange(device uuid.UUID, currentState ygot.GoStruct, change ygot.GoStruct, callback func(ygot.GoStruct, ygot.GoStruct) error, errChan chan error) *Change {
    
    	return &Change{
    		cuid:          uuid.New(),
    		duid:          device,
    		timestamp:     time.Now(),
    		previousState: currentState,
    		intendedState: change,
    		committed:     false,
    		confirmed:     false,
    		callback:      callback,
    
    		errChan:       errChan,
    		Done:          make(chan int),
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // Change is an intended change to an OND. It is unique and immutable.
    
    // It has a cuid, a timestamp, and holds both the previous and the new
    // state. It keeps track if the state is committed and confirmed. A callback
    // exists to acess the proper transport for the changed OND
    type Change struct {
    	cuid          uuid.UUID
    	duid          uuid.UUID
    	timestamp     time.Time
    	previousState ygot.GoStruct
    	intendedState ygot.GoStruct
    	committed     bool
    	confirmed     bool
    
    	inconsistent  bool
    
    	callback      func(ygot.GoStruct, ygot.GoStruct) error
    	lock          sync.RWMutex
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	cancelFunc    context.CancelFunc
    
    	errChan       chan error
    	// TODO: Move nucleus.pndImplementation and Change to same package and unexport
    	Done chan int
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // ID returns the Change's UUID
    
    func (c *Change) ID() uuid.UUID {
    	return c.cuid
    }
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // Commit pushes the cange to the OND using the callback() function
    // and starts the timeout-timer for the Change. If the timer expires
    // the change is rolled back.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (c *Change) Commit() error {
    
    	if err := c.callback(c.intendedState, c.previousState); err != nil {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    		return err
    
    	c.committed = true
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	log.WithFields(log.Fields{
    		"change uuid": c.cuid,
    		"device uuid": c.duid,
    	}).Debug("change commited")
    	ctx, cancel := context.WithCancel(context.Background())
    	c.cancelFunc = cancel
    	go c.rollbackHandler(ctx)
    	return nil
    }
    
    func (c *Change) rollbackHandler(ctx context.Context) {
    	select {
    	case <-ctx.Done():
    		return
    	case <-time.Tick(changeTimeout):
    
    		c.lock.RLock()
    		defer c.lock.RUnlock()
    		if !c.confirmed {
    
    			c.errChan <- c.callback(c.previousState, c.intendedState)
    
    			log.WithFields(log.Fields{
    				"change uuid": c.cuid,
    				"device uuid": c.duid,
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			}).Info("change timed out")
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // Confirm confirms a committed Change and stops the rollback timer.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func (c *Change) Confirm() error {
    	c.lock.RLock()
    	if !c.committed {
    		defer c.lock.RUnlock()
    		return errors.New("cannot confirm uncommitted change")
    	}
    	c.lock.RUnlock()
    
    	c.lock.Lock()
    	defer c.lock.Unlock()
    	c.confirmed = true
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	c.cancelFunc()
    
    	close(c.errChan)
    	c.Done <- 0
    	close(c.Done)
    
    	log.WithFields(log.Fields{
    		"change uuid": c.cuid,
    		"device uuid": c.duid,
    	}).Info("change confirmed")
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	return nil