github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/meterstatus.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	jujutxn "github.com/juju/txn"
     9  	"gopkg.in/mgo.v2/bson"
    10  	"gopkg.in/mgo.v2/txn"
    11  )
    12  
    13  // MeterStatus represents the metering status of a unit.
    14  type MeterStatus struct {
    15  	Code MeterStatusCode
    16  	Info string
    17  }
    18  
    19  // Severity returns relative severity of the meter status.
    20  func (m *MeterStatus) Severity() int {
    21  	return m.Code.Severity()
    22  }
    23  
    24  // MeterStatusCode represents the meter status code of a unit.
    25  // The int value represents its relative severity when compared to
    26  // other MeterStatusCodes.
    27  type MeterStatusCode int
    28  
    29  // Severity returns the relative severity.
    30  func (m MeterStatusCode) Severity() int {
    31  	return int(m)
    32  }
    33  
    34  // String returns a human readable string representation of the meter status.
    35  func (m MeterStatusCode) String() string {
    36  	s, ok := meterString[m]
    37  	if !ok {
    38  		return MeterNotAvailable.String()
    39  	}
    40  	return s
    41  }
    42  
    43  // MeterStatusFromString returns a valid MeterStatusCode given a string representation.
    44  func MeterStatusFromString(str string) MeterStatusCode {
    45  	for m, s := range meterString {
    46  		if s == str {
    47  			return m
    48  		}
    49  	}
    50  	return MeterNotAvailable
    51  }
    52  
    53  // This const block defines the relative severities of the valid MeterStatusCodes in ascending order.
    54  const (
    55  	MeterNotAvailable MeterStatusCode = iota
    56  	MeterRed
    57  	MeterAmber
    58  	MeterNotSet
    59  	MeterGreen
    60  )
    61  
    62  var (
    63  	meterString = map[MeterStatusCode]string{
    64  		MeterGreen:        "GREEN",
    65  		MeterNotSet:       "NOT SET",
    66  		MeterAmber:        "AMBER",
    67  		MeterNotAvailable: "NOT AVAILABLE",
    68  		MeterRed:          "RED",
    69  	}
    70  )
    71  
    72  type meterStatusDoc struct {
    73  	DocID     string `bson:"_id"`
    74  	ModelUUID string `bson:"model-uuid"`
    75  	Code      string `bson:"code"`
    76  	Info      string `bson:"info"`
    77  }
    78  
    79  // SetMeterStatus sets the meter status for the unit.
    80  func (u *Unit) SetMeterStatus(codeStr, info string) error {
    81  	code := MeterStatusFromString(codeStr)
    82  	switch code {
    83  	case MeterGreen, MeterAmber, MeterRed:
    84  	default:
    85  		return errors.Errorf("invalid meter status %q", code)
    86  	}
    87  	meterDoc, err := u.getMeterStatusDoc()
    88  	if err != nil {
    89  		return errors.Annotatef(err, "cannot update meter status for unit %s", u.Name())
    90  	}
    91  	if meterDoc.Code == code.String() && meterDoc.Info == info {
    92  		return nil
    93  	}
    94  
    95  	buildTxn := func(attempt int) ([]txn.Op, error) {
    96  		if attempt > 0 {
    97  			err := u.Refresh()
    98  			if err != nil {
    99  				return nil, errors.Trace(err)
   100  			}
   101  			meterDoc, err = u.getMeterStatusDoc()
   102  			if err != nil {
   103  				return nil, errors.Annotatef(err, "cannot update meter status for unit %s", u.Name())
   104  			}
   105  			if meterDoc.Code == code.String() && meterDoc.Info == info {
   106  				return nil, jujutxn.ErrNoOperations
   107  			}
   108  		}
   109  		return []txn.Op{
   110  			{
   111  				C:      unitsC,
   112  				Id:     u.doc.DocID,
   113  				Assert: isAliveDoc,
   114  			}, {
   115  				C:      meterStatusC,
   116  				Id:     u.st.docID(u.globalMeterStatusKey()),
   117  				Assert: txn.DocExists,
   118  				Update: bson.D{{"$set", bson.D{{"code", code.String()}, {"info", info}}}},
   119  			}}, nil
   120  	}
   121  	return errors.Annotatef(u.st.run(buildTxn), "cannot set meter state for unit %s", u.Name())
   122  }
   123  
   124  // createMeterStatusOp returns the operation needed to create the meter status
   125  // document associated with the given globalKey.
   126  func createMeterStatusOp(st *State, globalKey string, doc *meterStatusDoc) txn.Op {
   127  	doc.ModelUUID = st.ModelUUID()
   128  	return txn.Op{
   129  		C:      meterStatusC,
   130  		Id:     st.docID(globalKey),
   131  		Assert: txn.DocMissing,
   132  		Insert: doc,
   133  	}
   134  }
   135  
   136  // removeMeterStatusOp returns the operation needed to remove the meter status
   137  // document associated with the given globalKey.
   138  func removeMeterStatusOp(st *State, globalKey string) txn.Op {
   139  	return txn.Op{
   140  		C:      meterStatusC,
   141  		Id:     st.docID(globalKey),
   142  		Remove: true,
   143  	}
   144  }
   145  
   146  // GetMeterStatus returns the meter status for the unit.
   147  func (u *Unit) GetMeterStatus() (MeterStatus, error) {
   148  	mm, err := u.st.MetricsManager()
   149  	if err != nil {
   150  		return MeterStatus{MeterNotAvailable, ""}, errors.Annotatef(err, "cannot retrieve meter status for metrics manager")
   151  	}
   152  
   153  	mmStatus := mm.MeterStatus()
   154  	if mmStatus.Code == MeterRed {
   155  		return mmStatus, nil
   156  	}
   157  
   158  	status, err := u.getMeterStatusDoc()
   159  	if err != nil {
   160  		return MeterStatus{MeterNotAvailable, ""}, errors.Annotatef(err, "cannot retrieve meter status for unit %s", u.Name())
   161  	}
   162  
   163  	code := MeterStatusFromString(status.Code)
   164  
   165  	unitMeterStatus := MeterStatus{code, status.Info}
   166  	return combineMeterStatus(mmStatus, unitMeterStatus), nil
   167  }
   168  
   169  func combineMeterStatus(a, b MeterStatus) MeterStatus {
   170  	if a.Severity() < b.Severity() {
   171  		return a
   172  	}
   173  	return b
   174  }
   175  
   176  func (u *Unit) getMeterStatusDoc() (*meterStatusDoc, error) {
   177  	meterStatuses, closer := u.st.getCollection(meterStatusC)
   178  	defer closer()
   179  	var status meterStatusDoc
   180  	err := meterStatuses.FindId(u.globalMeterStatusKey()).One(&status)
   181  	if err != nil {
   182  		return nil, errors.Trace(err)
   183  	}
   184  	return &status, nil
   185  }