github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/relation.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	stderrors "errors"
     8  	"fmt"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names"
    15  	jujutxn "github.com/juju/txn"
    16  	"gopkg.in/juju/charm.v6-unstable"
    17  	"gopkg.in/mgo.v2"
    18  	"gopkg.in/mgo.v2/bson"
    19  	"gopkg.in/mgo.v2/txn"
    20  )
    21  
    22  // relationKey returns a string describing the relation defined by
    23  // endpoints, for use in various contexts (including error messages).
    24  func relationKey(endpoints []Endpoint) string {
    25  	eps := epSlice{}
    26  	for _, ep := range endpoints {
    27  		eps = append(eps, ep)
    28  	}
    29  	sort.Sort(eps)
    30  	names := []string{}
    31  	for _, ep := range eps {
    32  		names = append(names, ep.String())
    33  	}
    34  	return strings.Join(names, " ")
    35  }
    36  
    37  // relationDoc is the internal representation of a Relation in MongoDB.
    38  // Note the correspondence with RelationInfo in apiserver/params.
    39  type relationDoc struct {
    40  	DocID     string `bson:"_id"`
    41  	Key       string `bson:"key"`
    42  	ModelUUID string `bson:"model-uuid"`
    43  	Id        int
    44  	Endpoints []Endpoint
    45  	Life      Life
    46  	UnitCount int
    47  }
    48  
    49  // Relation represents a relation between one or two service endpoints.
    50  type Relation struct {
    51  	st  *State
    52  	doc relationDoc
    53  }
    54  
    55  func newRelation(st *State, doc *relationDoc) *Relation {
    56  	return &Relation{
    57  		st:  st,
    58  		doc: *doc,
    59  	}
    60  }
    61  
    62  func (r *Relation) String() string {
    63  	return r.doc.Key
    64  }
    65  
    66  // Tag returns a name identifying the relation.
    67  func (r *Relation) Tag() names.Tag {
    68  	return names.NewRelationTag(r.doc.Key)
    69  }
    70  
    71  // Refresh refreshes the contents of the relation from the underlying
    72  // state. It returns an error that satisfies errors.IsNotFound if the
    73  // relation has been removed.
    74  func (r *Relation) Refresh() error {
    75  	relations, closer := r.st.getCollection(relationsC)
    76  	defer closer()
    77  
    78  	doc := relationDoc{}
    79  	err := relations.FindId(r.doc.DocID).One(&doc)
    80  	if err == mgo.ErrNotFound {
    81  		return errors.NotFoundf("relation %v", r)
    82  	}
    83  	if err != nil {
    84  		return errors.Annotatef(err, "cannot refresh relation %v", r)
    85  	}
    86  	if r.doc.Id != doc.Id {
    87  		// The relation has been destroyed and recreated. This is *not* the
    88  		// same relation; if we pretend it is, we run the risk of violating
    89  		// the lifecycle-only-advances guarantee.
    90  		return errors.NotFoundf("relation %v", r)
    91  	}
    92  	r.doc = doc
    93  	return nil
    94  }
    95  
    96  // Life returns the relation's current life state.
    97  func (r *Relation) Life() Life {
    98  	return r.doc.Life
    99  }
   100  
   101  // Destroy ensures that the relation will be removed at some point; if no units
   102  // are currently in scope, it will be removed immediately.
   103  func (r *Relation) Destroy() (err error) {
   104  	defer errors.DeferredAnnotatef(&err, "cannot destroy relation %q", r)
   105  	if len(r.doc.Endpoints) == 1 && r.doc.Endpoints[0].Role == charm.RolePeer {
   106  		return errors.Errorf("is a peer relation")
   107  	}
   108  	defer func() {
   109  		if err == nil {
   110  			// This is a white lie; the document might actually be removed.
   111  			r.doc.Life = Dying
   112  		}
   113  	}()
   114  	rel := &Relation{r.st, r.doc}
   115  	// In this context, aborted transactions indicate that the number of units
   116  	// in scope have changed between 0 and not-0. The chances of 5 successive
   117  	// attempts each hitting this change -- which is itself an unlikely one --
   118  	// are considered to be extremely small.
   119  	buildTxn := func(attempt int) ([]txn.Op, error) {
   120  		if attempt > 0 {
   121  			if err := rel.Refresh(); errors.IsNotFound(err) {
   122  				return []txn.Op{}, nil
   123  			} else if err != nil {
   124  				return nil, err
   125  			}
   126  		}
   127  		ops, _, err := rel.destroyOps("")
   128  		if err == errAlreadyDying {
   129  			return nil, jujutxn.ErrNoOperations
   130  		} else if err != nil {
   131  			return nil, err
   132  		}
   133  		return ops, nil
   134  	}
   135  	return rel.st.run(buildTxn)
   136  }
   137  
   138  var errAlreadyDying = stderrors.New("entity is already dying and cannot be destroyed")
   139  
   140  // destroyOps returns the operations necessary to destroy the relation, and
   141  // whether those operations will lead to the relation's removal. These
   142  // operations may include changes to the relation's services; however, if
   143  // ignoreService is not empty, no operations modifying that service will
   144  // be generated.
   145  func (r *Relation) destroyOps(ignoreService string) (ops []txn.Op, isRemove bool, err error) {
   146  	if r.doc.Life != Alive {
   147  		return nil, false, errAlreadyDying
   148  	}
   149  	if r.doc.UnitCount == 0 {
   150  		removeOps, err := r.removeOps(ignoreService, nil)
   151  		if err != nil {
   152  			return nil, false, err
   153  		}
   154  		return removeOps, true, nil
   155  	}
   156  	return []txn.Op{{
   157  		C:      relationsC,
   158  		Id:     r.doc.DocID,
   159  		Assert: bson.D{{"life", Alive}, {"unitcount", bson.D{{"$gt", 0}}}},
   160  		Update: bson.D{{"$set", bson.D{{"life", Dying}}}},
   161  	}}, false, nil
   162  }
   163  
   164  // removeOps returns the operations necessary to remove the relation. If
   165  // ignoreService is not empty, no operations affecting that service will be
   166  // included; if departingUnit is not nil, this implies that the relation's
   167  // services may be Dying and otherwise unreferenced, and may thus require
   168  // removal themselves.
   169  func (r *Relation) removeOps(ignoreService string, departingUnit *Unit) ([]txn.Op, error) {
   170  	relOp := txn.Op{
   171  		C:      relationsC,
   172  		Id:     r.doc.DocID,
   173  		Remove: true,
   174  	}
   175  	if departingUnit != nil {
   176  		relOp.Assert = bson.D{{"life", Dying}, {"unitcount", 1}}
   177  	} else {
   178  		relOp.Assert = bson.D{{"life", Alive}, {"unitcount", 0}}
   179  	}
   180  	ops := []txn.Op{relOp}
   181  	for _, ep := range r.doc.Endpoints {
   182  		if ep.ServiceName == ignoreService {
   183  			continue
   184  		}
   185  		var asserts bson.D
   186  		hasRelation := bson.D{{"relationcount", bson.D{{"$gt", 0}}}}
   187  		if departingUnit == nil {
   188  			// We're constructing a destroy operation, either of the relation
   189  			// or one of its services, and can therefore be assured that both
   190  			// services are Alive.
   191  			asserts = append(hasRelation, isAliveDoc...)
   192  		} else if ep.ServiceName == departingUnit.ServiceName() {
   193  			// This service must have at least one unit -- the one that's
   194  			// departing the relation -- so it cannot be ready for removal.
   195  			cannotDieYet := bson.D{{"unitcount", bson.D{{"$gt", 0}}}}
   196  			asserts = append(hasRelation, cannotDieYet...)
   197  		} else {
   198  			// This service may require immediate removal.
   199  			services, closer := r.st.getCollection(servicesC)
   200  			defer closer()
   201  
   202  			svc := &Service{st: r.st}
   203  			hasLastRef := bson.D{{"life", Dying}, {"unitcount", 0}, {"relationcount", 1}}
   204  			removable := append(bson.D{{"_id", ep.ServiceName}}, hasLastRef...)
   205  			if err := services.Find(removable).One(&svc.doc); err == nil {
   206  				ops = append(ops, svc.removeOps(hasLastRef)...)
   207  				continue
   208  			} else if err != mgo.ErrNotFound {
   209  				return nil, err
   210  			}
   211  			// If not, we must check that this is still the case when the
   212  			// transaction is applied.
   213  			asserts = bson.D{{"$or", []bson.D{
   214  				{{"life", Alive}},
   215  				{{"unitcount", bson.D{{"$gt", 0}}}},
   216  				{{"relationcount", bson.D{{"$gt", 1}}}},
   217  			}}}
   218  		}
   219  		ops = append(ops, txn.Op{
   220  			C:      servicesC,
   221  			Id:     r.st.docID(ep.ServiceName),
   222  			Assert: asserts,
   223  			Update: bson.D{{"$inc", bson.D{{"relationcount", -1}}}},
   224  		})
   225  	}
   226  	cleanupOp := r.st.newCleanupOp(cleanupRelationSettings, fmt.Sprintf("r#%d#", r.Id()))
   227  	return append(ops, cleanupOp), nil
   228  }
   229  
   230  // Id returns the integer internal relation key. This is exposed
   231  // because the unit agent needs to expose a value derived from this
   232  // (as JUJU_RELATION_ID) to allow relation hooks to differentiate
   233  // between relations with different services.
   234  func (r *Relation) Id() int {
   235  	return r.doc.Id
   236  }
   237  
   238  // Endpoint returns the endpoint of the relation for the named service.
   239  // If the service is not part of the relation, an error will be returned.
   240  func (r *Relation) Endpoint(serviceName string) (Endpoint, error) {
   241  	for _, ep := range r.doc.Endpoints {
   242  		if ep.ServiceName == serviceName {
   243  			return ep, nil
   244  		}
   245  	}
   246  	return Endpoint{}, errors.Errorf("service %q is not a member of %q", serviceName, r)
   247  }
   248  
   249  // Endpoints returns the endpoints for the relation.
   250  func (r *Relation) Endpoints() []Endpoint {
   251  	return r.doc.Endpoints
   252  }
   253  
   254  // RelatedEndpoints returns the endpoints of the relation r with which
   255  // units of the named service will establish relations. If the service
   256  // is not part of the relation r, an error will be returned.
   257  func (r *Relation) RelatedEndpoints(serviceName string) ([]Endpoint, error) {
   258  	local, err := r.Endpoint(serviceName)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	role := counterpartRole(local.Role)
   263  	var eps []Endpoint
   264  	for _, ep := range r.doc.Endpoints {
   265  		if ep.Role == role {
   266  			eps = append(eps, ep)
   267  		}
   268  	}
   269  	if eps == nil {
   270  		return nil, errors.Errorf("no endpoints of %q relate to service %q", r, serviceName)
   271  	}
   272  	return eps, nil
   273  }
   274  
   275  // Unit returns a RelationUnit for the supplied unit.
   276  func (r *Relation) Unit(u *Unit) (*RelationUnit, error) {
   277  	ep, err := r.Endpoint(u.doc.Service)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	scope := []string{"r", strconv.Itoa(r.doc.Id)}
   282  	if ep.Scope == charm.ScopeContainer {
   283  		container := u.doc.Principal
   284  		if container == "" {
   285  			container = u.doc.Name
   286  		}
   287  		scope = append(scope, container)
   288  	}
   289  	return &RelationUnit{
   290  		st:       r.st,
   291  		relation: r,
   292  		unit:     u,
   293  		endpoint: ep,
   294  		scope:    strings.Join(scope, "#"),
   295  	}, nil
   296  }