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