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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"gopkg.in/juju/charm.v6-unstable"
    11  	"gopkg.in/mgo.v2/bson"
    12  	"gopkg.in/mgo.v2/txn"
    13  
    14  	"github.com/juju/juju/mongo"
    15  	"github.com/juju/juju/payload"
    16  )
    17  
    18  // payloadDoc is the top-level document for payloads.
    19  type payloadDoc struct {
    20  	// _id encodes UnitID and Name (which should theoretically
    21  	// match the name of a payload-class defined in the charm --
    22  	// for example "my-payload" -- but nothing really checks).
    23  	UnitID string `bson:"unitid"`
    24  	Name   string `bson:"name"`
    25  
    26  	// MachineID doesn't belong here.
    27  	MachineID string `bson:"machine-id"`
    28  
    29  	// Type is again a freeform field that might match that of a
    30  	// payload-class defined in the charm -- for example, "docker".
    31  	Type string `bson:"type"`
    32  
    33  	// RawID records the substrate-specific payload id -- for
    34  	// example, "9cd6338abdf09beb", the actual docker container
    35  	// we're tracking.
    36  	RawID string `bson:"rawid"`
    37  
    38  	// State is sort of like status, valid values are defined in
    39  	// package payloads.
    40  	State string `bson:"state"`
    41  
    42  	// Labels contain whatever additional arbitrary strings were
    43  	// left over after we hoovered up <Type> <Name> <RawID> from the
    44  	// command line.
    45  	Labels []string `bson:"labels"`
    46  }
    47  
    48  // nsPayloads_ backs nsPayloads.
    49  type nsPayloads_ struct{}
    50  
    51  // nsPayloads namespaces low-level unit-payload functionality: it's
    52  // meant to be the one place in the code where we wrangle queries,
    53  // serialization, and updates to payload data. (The UnitPayloads and
    54  // ModelPayloads types may run queries directly, because it's silly
    55  // to build *another* mongo-aping layer with its own idiosyncratic
    56  // implementations of One and All and so on; but they should be getting
    57  // all their queries from here, and using these methods to convert
    58  // types, and generally making a point of *not* knowing anything about
    59  // how the actual data is represented.)
    60  var nsPayloads = nsPayloads_{}
    61  
    62  // docID is globalKey as written by someone who thought it would be
    63  // helpful to reinvent the 'u#<unit>#' prefix (which *would* indicate
    64  // that payloads are things-that-exist-per-unit, and do so in a way
    65  // consistent with the rest of the DB. /sigh.)
    66  func (nsPayloads_) docID(unit, name string) string {
    67  	return fmt.Sprintf("payload#%s#%s", unit, name)
    68  }
    69  
    70  // forUnit returns a selector that matches all payloads for the unit.
    71  func (nsPayloads_) forUnit(unit string) bson.D {
    72  	return bson.D{{"unitid", unit}}
    73  }
    74  
    75  // forUnitWithNames returns a selector that matches any payloads for the
    76  // supplied unit that have the supplied names.
    77  func (nsPayloads_) forUnitWithNames(unit string, names []string) bson.D {
    78  	ids := make([]string, 0, len(names))
    79  	for _, name := range names {
    80  		ids = append(ids, nsPayloads.docID(unit, name))
    81  	}
    82  	return bson.D{{"_id", bson.D{{"$in", ids}}}}
    83  }
    84  
    85  // asDoc converts a FullPayloadInfo into an independent payloadDoc.
    86  func (nsPayloads_) asDoc(p payload.FullPayloadInfo) payloadDoc {
    87  	labels := make([]string, len(p.Labels))
    88  	copy(labels, p.Labels)
    89  	return payloadDoc{
    90  		UnitID:    p.Unit,
    91  		Name:      p.PayloadClass.Name,
    92  		MachineID: p.Machine,
    93  		Type:      p.PayloadClass.Type,
    94  		RawID:     p.ID,
    95  		State:     p.Status,
    96  		Labels:    labels,
    97  	}
    98  }
    99  
   100  // asPayload converts a payloadDoc into an independent FullPayloadInfo.
   101  func (nsPayloads_) asPayload(doc payloadDoc) payload.FullPayloadInfo {
   102  	labels := make([]string, len(doc.Labels))
   103  	copy(labels, doc.Labels)
   104  	p := payload.FullPayloadInfo{
   105  		Payload: payload.Payload{
   106  			PayloadClass: charm.PayloadClass{
   107  				Name: doc.Name,
   108  				Type: doc.Type,
   109  			},
   110  			ID:     doc.RawID,
   111  			Status: doc.State,
   112  			Labels: labels,
   113  			Unit:   doc.UnitID,
   114  		},
   115  		Machine: doc.MachineID,
   116  	}
   117  	return p
   118  }
   119  
   120  // asPayloads converts a slice of payloadDocs into a corresponding slice
   121  // of independent FullPayloadInfos.
   122  func (nsPayloads_) asPayloads(docs []payloadDoc) []payload.FullPayloadInfo {
   123  	payloads := make([]payload.FullPayloadInfo, 0, len(docs))
   124  	for _, doc := range docs {
   125  		payloads = append(payloads, nsPayloads.asPayload(doc))
   126  	}
   127  	return payloads
   128  }
   129  
   130  // asResults converts a slice of payloadDocs into a corresponding slice
   131  // of independent payload.Results.
   132  func (nsPayloads_) asResults(docs []payloadDoc) []payload.Result {
   133  	results := make([]payload.Result, 0, len(docs))
   134  	for _, doc := range docs {
   135  		full := nsPayloads.asPayload(doc)
   136  		results = append(results, payload.Result{
   137  			ID:      doc.Name,
   138  			Payload: &full,
   139  		})
   140  	}
   141  	return results
   142  }
   143  
   144  // orderedResults converts payloadDocs into payload.Results, in the
   145  // order defined by names, and represents missing names in the highly
   146  // baroque fashion apparently designed into Results.
   147  func (nsPayloads_) orderedResults(docs []payloadDoc, names []string) []payload.Result {
   148  	found := make(map[string]payloadDoc)
   149  	for _, doc := range docs {
   150  		found[doc.Name] = doc
   151  	}
   152  	results := make([]payload.Result, len(names))
   153  	for i, name := range names {
   154  		results[i].ID = name
   155  		if doc, ok := found[name]; ok {
   156  			full := nsPayloads.asPayload(doc)
   157  			results[i].Payload = &full
   158  		} else {
   159  			results[i].NotFound = true
   160  			results[i].Error = errors.NotFoundf(name)
   161  		}
   162  	}
   163  	return results
   164  }
   165  
   166  // trackOp returns a txn.Op that will either insert or update the
   167  // supplied payload, and fail if the observed precondition changes.
   168  func (nsPayloads_) trackOp(payloads mongo.Collection, doc payloadDoc) (txn.Op, error) {
   169  	docID := nsPayloads.docID(doc.UnitID, doc.Name)
   170  	payloadOp := txn.Op{
   171  		C:  payloads.Name(),
   172  		Id: docID,
   173  	}
   174  	count, err := payloads.FindId(docID).Count()
   175  	if err != nil {
   176  		return txn.Op{}, errors.Trace(err)
   177  	} else if count == 0 {
   178  		payloadOp.Assert = txn.DocMissing
   179  		payloadOp.Insert = doc
   180  	} else {
   181  		payloadOp.Assert = txn.DocExists
   182  		payloadOp.Update = bson.D{{"$set", doc}}
   183  	}
   184  	return payloadOp, nil
   185  }
   186  
   187  // untrackOp returns a txn.Op that will unconditionally remove the
   188  // identified document. If the payload doesn't exist, it returns
   189  // errAlreadyRemoved.
   190  func (nsPayloads_) untrackOp(payloads mongo.Collection, docID string) (txn.Op, error) {
   191  	count, err := payloads.FindId(docID).Count()
   192  	if err != nil {
   193  		return txn.Op{}, errors.Trace(err)
   194  	} else if count == 0 {
   195  		return txn.Op{}, errAlreadyRemoved
   196  	}
   197  	return txn.Op{
   198  		C:      payloads.Name(),
   199  		Id:     docID,
   200  		Assert: txn.DocExists,
   201  		Remove: true,
   202  	}, nil
   203  }
   204  
   205  // setStatusOp returns a txn.Op that updates the status of the
   206  // identified payload. If the payload doesn't exist, it returns
   207  // errAlreadyRemoved.
   208  func (nsPayloads_) setStatusOp(payloads mongo.Collection, docID string, status string) (txn.Op, error) {
   209  	count, err := payloads.FindId(docID).Count()
   210  	if err != nil {
   211  		return txn.Op{}, errors.Trace(err)
   212  	} else if count == 0 {
   213  		return txn.Op{}, errAlreadyRemoved
   214  	}
   215  	return txn.Op{
   216  		C:      payloads.Name(),
   217  		Id:     docID,
   218  		Assert: txn.DocExists,
   219  		Update: bson.D{{"$set", bson.D{{"state", status}}}},
   220  	}, nil
   221  }