github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/lease/schema.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lease 5 6 import ( 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/juju/errors" 12 13 "github.com/juju/juju/core/lease" 14 ) 15 16 // These constants define the field names and type values used by documents in 17 // a lease collection. They *must* remain in sync with the bson marshalling 18 // annotations in leaseDoc. 19 const ( 20 // field* identify the fields in a leaseDoc. 21 fieldNamespace = "namespace" 22 fieldHolder = "holder" 23 fieldStart = "start" 24 fieldDuration = "duration" 25 fieldWriter = "writer" 26 ) 27 28 // toInt64 converts a local time.Time into a database value that doesn't 29 // silently lose precision. 30 func toInt64(t time.Time) int64 { 31 return t.UnixNano() 32 } 33 34 // toTime converts a toInt64 result, as loaded from the db, back to a time.Time. 35 func toTime(v int64) time.Time { 36 return time.Unix(0, v) 37 } 38 39 // leaseDocId returns the _id for the document holding details of the supplied 40 // namespace and lease. 41 func leaseDocId(namespace, lease string) string { 42 return fmt.Sprintf("%s#%s#", namespace, lease) 43 } 44 45 // leaseDoc is used to serialise lease entries. 46 type leaseDoc struct { 47 // Id is always "<Namespace>#<Name>#", so that we can extract useful 48 // information from a stream of watcher events without incurring extra 49 // DB hits. Apart from checking validity on load, though, there's 50 // little reason to use Id elsewhere; Namespace and Name are the 51 // sources of truth. 52 Id string `bson:"_id"` 53 Namespace string `bson:"namespace"` 54 Name string `bson:"name"` 55 56 // Holder, Expiry, and Writer map directly to entry. 57 Holder string `bson:"holder"` 58 Start int64 `bson:"start"` 59 Duration time.Duration `bson:"duration"` 60 Writer string `bson:"writer"` 61 } 62 63 // validate returns an error if any fields are invalid or inconsistent. 64 func (doc leaseDoc) validate() error { 65 // state.multiModelRunner prepends environ ids in our documents, and 66 // state.modelStateCollection does not strip them out. 67 if !strings.HasSuffix(doc.Id, leaseDocId(doc.Namespace, doc.Name)) { 68 return errors.Errorf("inconsistent _id") 69 } 70 if err := lease.ValidateString(doc.Holder); err != nil { 71 return errors.Annotatef(err, "invalid holder") 72 } 73 if doc.Start < 0 { 74 return errors.Errorf("invalid start time") 75 } 76 if doc.Duration <= 0 { 77 return errors.Errorf("invalid duration") 78 } 79 if err := lease.ValidateString(doc.Writer); err != nil { 80 return errors.Annotatef(err, "invalid writer") 81 } 82 return nil 83 } 84 85 // entry returns the lease name and an entry corresponding to the document. If 86 // the document cannot be validated, it returns an error. 87 func (doc leaseDoc) entry() (string, entry, error) { 88 if err := doc.validate(); err != nil { 89 return "", entry{}, errors.Trace(err) 90 } 91 entry := entry{ 92 holder: doc.Holder, 93 start: toTime(doc.Start), 94 duration: doc.Duration, 95 writer: doc.Writer, 96 } 97 return doc.Name, entry, nil 98 } 99 100 // newLeaseDoc returns a valid lease document encoding the supplied lease and 101 // entry in the supplied namespace, or an error. 102 func newLeaseDoc(namespace, name string, entry entry) (*leaseDoc, error) { 103 doc := &leaseDoc{ 104 Id: leaseDocId(namespace, name), 105 Namespace: namespace, 106 Name: name, 107 Holder: entry.holder, 108 Start: toInt64(entry.start), 109 Duration: entry.duration, 110 Writer: entry.writer, 111 } 112 if err := doc.validate(); err != nil { 113 return nil, errors.Trace(err) 114 } 115 return doc, nil 116 }