github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 and clockDoc. 19 const ( 20 // fieldType and fieldNamespace identify the Type and Namespace fields in 21 // both leaseDoc and clockDoc. 22 fieldType = "type" 23 fieldNamespace = "namespace" 24 25 // typeLease and typeClock are the acceptable values for fieldType. 26 typeLease = "lease" 27 typeClock = "clock" 28 29 // fieldLease* identify the fields in a leaseDoc. 30 fieldLeaseHolder = "holder" 31 fieldLeaseExpiry = "expiry" 32 fieldLeaseWriter = "writer" 33 34 // fieldClock* identify the fields in a clockDoc. 35 fieldClockWriters = "writers" 36 ) 37 38 // toInt64 converts a local time.Time into a database value that doesn't 39 // silently lose precision. 40 func toInt64(t time.Time) int64 { 41 return t.UnixNano() 42 } 43 44 // toTime converts a toInt64 result, as loaded from the db, back to a time.Time. 45 func toTime(v int64) time.Time { 46 return time.Unix(0, v) 47 } 48 49 // leaseDocId returns the _id for the document holding details of the supplied 50 // namespace and lease. 51 func leaseDocId(namespace, lease string) string { 52 return fmt.Sprintf("%s#%s#%s#", typeLease, namespace, lease) 53 } 54 55 // leaseDoc is used to serialise lease entries. 56 type leaseDoc struct { 57 // Id is always "<Type>#<Namespace>#<Name>#", and <Type> is always "lease", 58 // so that we can extract useful information from a stream of watcher events 59 // without incurring extra DB hits. 60 // Apart from checking validity on load, though, there's little reason 61 // to use Id elsewhere; Namespace and Name are the sources of truth. 62 Id string `bson:"_id"` 63 Type string `bson:"type"` 64 Namespace string `bson:"namespace"` 65 Name string `bson:"name"` 66 67 // Holder, Expiry, and Writer map directly to entry. 68 Holder string `bson:"holder"` 69 Expiry int64 `bson:"expiry"` 70 Writer string `bson:"writer"` 71 } 72 73 // validate returns an error if any fields are invalid or inconsistent. 74 func (doc leaseDoc) validate() error { 75 if doc.Type != typeLease { 76 return errors.Errorf("invalid type %q", doc.Type) 77 } 78 // state.multiModelRunner prepends environ ids in our documents, and 79 // state.modelStateCollection does not strip them out. 80 if !strings.HasSuffix(doc.Id, leaseDocId(doc.Namespace, doc.Name)) { 81 return errors.Errorf("inconsistent _id") 82 } 83 if err := lease.ValidateString(doc.Holder); err != nil { 84 return errors.Annotatef(err, "invalid holder") 85 } 86 if doc.Expiry == 0 { 87 return errors.Errorf("invalid expiry") 88 } 89 if err := lease.ValidateString(doc.Writer); err != nil { 90 return errors.Annotatef(err, "invalid writer") 91 } 92 return nil 93 } 94 95 // entry returns the lease name and an entry corresponding to the document. If 96 // the document cannot be validated, it returns an error. 97 func (doc leaseDoc) entry() (string, entry, error) { 98 if err := doc.validate(); err != nil { 99 return "", entry{}, errors.Trace(err) 100 } 101 entry := entry{ 102 holder: doc.Holder, 103 expiry: toTime(doc.Expiry), 104 writer: doc.Writer, 105 } 106 return doc.Name, entry, nil 107 } 108 109 // newLeaseDoc returns a valid lease document encoding the supplied lease and 110 // entry in the supplied namespace, or an error. 111 func newLeaseDoc(namespace, name string, entry entry) (*leaseDoc, error) { 112 doc := &leaseDoc{ 113 Id: leaseDocId(namespace, name), 114 Type: typeLease, 115 Namespace: namespace, 116 Name: name, 117 Holder: entry.holder, 118 Expiry: toInt64(entry.expiry), 119 Writer: entry.writer, 120 } 121 if err := doc.validate(); err != nil { 122 return nil, errors.Trace(err) 123 } 124 return doc, nil 125 } 126 127 // clockDocId returns the _id for the document holding clock skew information 128 // for clients that have written in the supplied namespace. 129 func clockDocId(namespace string) string { 130 return fmt.Sprintf("%s#%s#", typeClock, namespace) 131 } 132 133 // clockDoc is used to synchronise clients. 134 type clockDoc struct { 135 // Id is always "<Type>#<Namespace>#", and <Type> is always "clock", for 136 // consistency with leaseDoc and ease of querying within the collection. 137 Id string `bson:"_id"` 138 Type string `bson:"type"` 139 Namespace string `bson:"namespace"` 140 141 // Writers holds the latest acknowledged time for every known client. 142 Writers map[string]int64 `bson:"writers"` 143 } 144 145 // validate returns an error if any fields are invalid or inconsistent. 146 func (doc clockDoc) validate() error { 147 if doc.Type != typeClock { 148 return errors.Errorf("invalid type %q", doc.Type) 149 } 150 // state.multiModelRunner prepends environ ids in our documents, and 151 // state.modelStateCollection does not strip them out. 152 if !strings.HasSuffix(doc.Id, clockDocId(doc.Namespace)) { 153 return errors.Errorf("inconsistent _id") 154 } 155 for writer, written := range doc.Writers { 156 if written == 0 { 157 return errors.Errorf("invalid time for writer %q", writer) 158 } 159 } 160 return nil 161 } 162 163 // skews returns clock skew information for all writers recorded in the 164 // document, given that the document was read between the supplied local 165 // times. It will return an error if the clock document is not valid, or 166 // if the times don't make sense. 167 func (doc clockDoc) skews(beginning, end time.Time) (map[string]Skew, error) { 168 if err := doc.validate(); err != nil { 169 return nil, errors.Trace(err) 170 } 171 // beginning is expected to be earlier than end. 172 // If it isn't, it could be ntp rolling the clock back slowly, so we add 173 // a little wiggle room here. 174 if end.Before(beginning) { 175 // A later time, subtract an earlier time will give a positive duration. 176 difference := beginning.Sub(end) 177 if difference > 10*time.Millisecond { 178 return nil, errors.Errorf("end of read window preceded beginning (%s)", difference) 179 180 } 181 beginning = end 182 } 183 skews := make(map[string]Skew) 184 for writer, written := range doc.Writers { 185 skews[writer] = Skew{ 186 LastWrite: toTime(written), 187 Beginning: beginning, 188 End: end, 189 } 190 } 191 return skews, nil 192 } 193 194 // newClockDoc returns an empty clockDoc for the supplied namespace. 195 func newClockDoc(namespace string) (*clockDoc, error) { 196 doc := &clockDoc{ 197 Id: clockDocId(namespace), 198 Type: typeClock, 199 Namespace: namespace, 200 Writers: make(map[string]int64), 201 } 202 if err := doc.validate(); err != nil { 203 return nil, errors.Trace(err) 204 } 205 return doc, nil 206 }