github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/reboot.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Copyright 2014 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package state
     6  
     7  import (
     8  	"fmt"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/mgo.v2"
    12  	"gopkg.in/mgo.v2/bson"
    13  	"gopkg.in/mgo.v2/txn"
    14  )
    15  
    16  var _ RebootFlagSetter = (*Machine)(nil)
    17  var _ RebootActionGetter = (*Machine)(nil)
    18  
    19  // RebootAction defines the action a machine should
    20  // take when a hook needs to reboot
    21  type RebootAction string
    22  
    23  const (
    24  	// ShouldDoNothing instructs a machine agent that no action
    25  	// is required on its part
    26  	ShouldDoNothing RebootAction = "noop"
    27  	// ShouldReboot instructs a machine to reboot
    28  	// this happens when a hook running on a machine, requests
    29  	// a reboot
    30  	ShouldReboot RebootAction = "reboot"
    31  	// ShouldShutdown instructs a machine to shut down. This usually
    32  	// happens when running inside a container, and a hook on the parent
    33  	// machine requests a reboot
    34  	ShouldShutdown RebootAction = "shutdown"
    35  )
    36  
    37  // rebootDoc will hold the reboot flag for a machine.
    38  type rebootDoc struct {
    39  	DocID     string `bson:"_id"`
    40  	Id        string `bson:"machineid"`
    41  	ModelUUID string `bson:"model-uuid"`
    42  }
    43  
    44  func (m *Machine) setFlag() error {
    45  	if m.Life() == Dead {
    46  		return mgo.ErrNotFound
    47  	}
    48  	ops := []txn.Op{
    49  		assertModelActiveOp(m.st.ModelUUID()),
    50  		{
    51  			C:      machinesC,
    52  			Id:     m.doc.DocID,
    53  			Assert: notDeadDoc,
    54  		}, {
    55  			C:      rebootC,
    56  			Id:     m.doc.DocID,
    57  			Insert: &rebootDoc{Id: m.Id()},
    58  		},
    59  	}
    60  	err := m.st.db().RunTransaction(ops)
    61  	if err == txn.ErrAborted {
    62  		if err := checkModelActive(m.st); err != nil {
    63  			return errors.Trace(err)
    64  		}
    65  		return mgo.ErrNotFound
    66  	} else if err != nil {
    67  		return errors.Errorf("failed to set reboot flag: %v", err)
    68  	}
    69  	return nil
    70  }
    71  
    72  func removeRebootDocOp(st *State, machineId string) txn.Op {
    73  	op := txn.Op{
    74  		C:      rebootC,
    75  		Id:     st.docID(machineId),
    76  		Remove: true,
    77  	}
    78  	return op
    79  }
    80  
    81  func (m *Machine) clearFlag() error {
    82  	reboot, closer := m.st.db().GetCollection(rebootC)
    83  	defer closer()
    84  
    85  	docID := m.doc.DocID
    86  	count, err := reboot.FindId(docID).Count()
    87  	if count == 0 {
    88  		return nil
    89  	}
    90  	ops := []txn.Op{removeRebootDocOp(m.st, m.Id())}
    91  	err = m.st.db().RunTransaction(ops)
    92  	if err != nil {
    93  		return errors.Errorf("failed to clear reboot flag: %v", err)
    94  	}
    95  	return nil
    96  }
    97  
    98  // SetRebootFlag sets the reboot flag of a machine to a boolean value. It will also
    99  // do a lazy create of a reboot document if needed; i.e. If a document
   100  // does not exist yet for this machine, it will create it.
   101  func (m *Machine) SetRebootFlag(flag bool) error {
   102  	if flag {
   103  		return m.setFlag()
   104  	}
   105  	return m.clearFlag()
   106  }
   107  
   108  // GetRebootFlag returns the reboot flag for this machine.
   109  func (m *Machine) GetRebootFlag() (bool, error) {
   110  	rebootCol, closer := m.st.db().GetCollection(rebootC)
   111  	defer closer()
   112  
   113  	count, err := rebootCol.FindId(m.doc.DocID).Count()
   114  	if err != nil {
   115  		return false, fmt.Errorf("failed to get reboot flag: %v", err)
   116  	}
   117  	if count == 0 {
   118  		return false, nil
   119  	}
   120  	return true, nil
   121  }
   122  
   123  func (m *Machine) machinesToCareAboutRebootsFor() []string {
   124  	var possibleIds []string
   125  	for currentId := m.Id(); currentId != ""; {
   126  		possibleIds = append(possibleIds, currentId)
   127  		currentId = ParentId(currentId)
   128  	}
   129  	return possibleIds
   130  }
   131  
   132  // ShouldRebootOrShutdown check if the current node should reboot or shutdown
   133  // If we are a container, and our parent needs to reboot, this should return:
   134  // ShouldShutdown
   135  func (m *Machine) ShouldRebootOrShutdown() (RebootAction, error) {
   136  	rebootCol, closer := m.st.db().GetCollection(rebootC)
   137  	defer closer()
   138  
   139  	machines := m.machinesToCareAboutRebootsFor()
   140  
   141  	docs := []rebootDoc{}
   142  	sel := bson.D{{"machineid", bson.D{{"$in", machines}}}}
   143  	if err := rebootCol.Find(sel).All(&docs); err != nil {
   144  		return ShouldDoNothing, errors.Trace(err)
   145  	}
   146  
   147  	iNeedReboot := false
   148  	for _, val := range docs {
   149  		if val.Id != m.doc.Id {
   150  			return ShouldShutdown, nil
   151  		}
   152  		iNeedReboot = true
   153  	}
   154  	if iNeedReboot {
   155  		return ShouldReboot, nil
   156  	}
   157  	return ShouldDoNothing, nil
   158  }
   159  
   160  type RebootFlagSetter interface {
   161  	SetRebootFlag(flag bool) error
   162  }
   163  
   164  type RebootActionGetter interface {
   165  	ShouldRebootOrShutdown() (RebootAction, error)
   166  }