services.go (8275B)
1 package services 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 "log" 8 "sermoni/internal/database" 9 "sermoni/internal/events" 10 11 "go.etcd.io/bbolt" 12 ) 13 14 var ( 15 keyServiceID = []byte("id") 16 keyServiceName = []byte("name") 17 keyServiceDescription = []byte("description") 18 keyServicePeriod = []byte("period") 19 keyServiceMaxEvents = []byte("maxevents") 20 ) 21 22 // Service describes a service that is expected to report 23 type Service struct { 24 ID uint64 `json:"id"` // service id, an integer that represents the service 25 Name string `json:"name"` // service name, usually on the format 'service @ server' 26 Description string `json:"description"` // more detailed description of the service 27 ExpectationPeriod uint64 `json:"period"` // set if the service is expected to report periodically, format is UnixTime (milli?) 28 MaxNumberEvents uint64 `json:"maxevents"` // max number of events to keep in DB at once - when limit is reached, oldest will be deleted 29 Token string `json:"token"` // service token - this is stored in the service-token bucket, not the services bucket 30 } 31 32 // GetByToken returns the service structure associated with the token string, if there 33 // are any matching entries in service-tokens bucket. Returns nil if there are no matches 34 func GetByToken(token string) *Service { 35 id := getIDFromToken(token) 36 if id == nil { 37 log.Printf("no service found for the token '%v'\n", token) 38 return nil 39 } 40 return get(id) 41 } 42 43 // GetByID returns the service structure associated with the given uint64-formatted 44 // service ID, if that service exists. Otherwise returns nil 45 func GetByID(id uint64) *Service { 46 idb := make([]byte, 8) 47 binary.BigEndian.PutUint64(idb, id) 48 return get(idb) 49 } 50 51 // GetAll returns all services in the database (TODO) 52 func GetAll() []*Service { 53 services := []*Service{} 54 db := database.GetDB() 55 db.View(func(tx *bbolt.Tx) error { 56 b := tx.Bucket(database.BucketKeyServices) 57 stb := tx.Bucket(database.BucketKeyServiceTokens) 58 if b == nil { 59 log.Panic("The services bucket does not exist") 60 } 61 if stb == nil { 62 log.Panic("The service-tokens bucket does not exist") 63 } 64 65 // Go through all k-v pairs in the service *tokens* bucket, in order to get service bucket IDs. 66 // Use the ID to get the service bucket and create service fromBucket, then set the service token 67 // using the key from the stb. The returned list of services will be sorted by *token*. 68 return stb.ForEach(func(token, id []byte) error { 69 sb := b.Bucket(id) 70 service := new(Service) 71 service.fromBucket(id, sb) 72 service.Token = string(token) 73 services = append(services, service) 74 return nil 75 }) 76 }) 77 return services 78 } 79 80 // Delete deletes the given service if it exists, and all events for said service, if any 81 func Delete(intID uint64) error { 82 db := database.GetDB() 83 serviceID := database.Uint64ToBytes(intID) 84 return db.Update(func(tx *bbolt.Tx) (err error) { 85 sb := tx.Bucket(database.BucketKeyServices) 86 stb := tx.Bucket(database.BucketKeyServiceTokens) 87 eb := tx.Bucket(database.BucketKeyEvents) 88 if sb == nil { 89 log.Panic("The services bucket does not exist") 90 } 91 if stb == nil { 92 log.Panic("The service-tokens bucket does not exist") 93 } 94 if eb == nil { 95 log.Panic("The event bucket does not exist") 96 } 97 98 // Delete the entry from root services bucket 99 if sb.Bucket(serviceID) == nil { 100 return errors.New("no service for the given id") 101 } 102 if err = sb.DeleteBucket(serviceID); err != nil { 103 return err 104 } 105 106 // Delete all events for the service 107 var eventsToDeleteIDs [][]byte 108 err = eb.ForEach(func(id, _ []byte) error { 109 evb := eb.Bucket(id) 110 event := new(events.Event) 111 if err := event.FromBucket(evb); err != nil { 112 return err 113 } 114 if event.Service == intID { 115 eventsToDeleteIDs = append(eventsToDeleteIDs, id) 116 } 117 return nil 118 }) 119 if err != nil { 120 return err 121 } 122 for _, id := range eventsToDeleteIDs { 123 if err = eb.DeleteBucket(id); err != nil { 124 fmt.Printf("Error deleting event with ID %v\n", id) 125 return err 126 } 127 } 128 129 // Find the token entry and delete it from service-tokens bucket 130 c := stb.Cursor() 131 for token, id := c.First(); token != nil; token, id = c.Next() { 132 if string(id) == string(serviceID) { 133 return stb.Delete(token) 134 } 135 } 136 return errors.New("service id not found in the service-tokens bucket") 137 138 // TODO: Cascade, i.e. delete all events for the given service 139 // Maybe this should be done in the HTTP request handler, though? 140 }) 141 } 142 143 // Add adds a new service to monitor 144 // Returns error if the token is unavailable and if the transaction fails in any way 145 func Add(service *Service) error { 146 db := database.GetDB() 147 return db.Update(func(tx *bbolt.Tx) error { 148 var err error 149 var b, sb, stb *bbolt.Bucket 150 var serviceIDint uint64 151 var serviceID []byte 152 153 // Get the services root bucket 154 if b = tx.Bucket(database.BucketKeyServices); b == nil { 155 log.Panic("The services bucket does not exist") 156 } 157 // Get the service-tokens root bucket 158 if stb = tx.Bucket(database.BucketKeyServiceTokens); stb == nil { 159 log.Panic("The service-tokens bucket does not exist") 160 } 161 162 // Check if the service token is available, return error otherwise 163 serviceToken := []byte(service.Token) 164 if serviceID = stb.Get(serviceToken); serviceID != nil { 165 return errors.New("a service has already been registered for the given token") 166 } 167 168 // Create a new service bucket, sb, and populate it with data from service 169 if serviceIDint, err = b.NextSequence(); err != nil { 170 return err 171 } 172 serviceID = database.Uint64ToBytes(serviceIDint) 173 if sb, err = b.CreateBucket(serviceID); err != nil { 174 return err 175 } 176 if err = sb.Put(keyServiceName, []byte(service.Name)); err != nil { 177 return err 178 } 179 if err = sb.Put(keyServiceDescription, []byte(service.Description)); err != nil { 180 return err 181 } 182 periodStr := database.Uint64ToBytes(service.ExpectationPeriod) 183 if err = sb.Put(keyServicePeriod, []byte(periodStr)); err != nil { 184 return err 185 } 186 maxEventsStr := database.Uint64ToBytes(service.MaxNumberEvents) 187 if err = sb.Put(keyServiceMaxEvents, []byte(maxEventsStr)); err != nil { 188 return err 189 } 190 191 // Put an entry in the service-tokens bucket to map the token to the service 192 return stb.Put([]byte(service.Token), serviceID) 193 }) 194 } 195 196 // 197 // Package-local helpers 198 // 199 200 // fromBucket populates the service struct with data from the given service bucket 201 // TODO: Consider failing on missing fields and generally choosing an approach more similar to Event.fromBucket 202 func (service *Service) fromBucket(id []byte, sb *bbolt.Bucket) { 203 idInt := database.BytesToUint64(id) 204 service.ID = idInt 205 if name := sb.Get(keyServiceName); name != nil { 206 service.Name = string(name) 207 } 208 if description := sb.Get(keyServiceDescription); description != nil { 209 service.Description = string(description) 210 } 211 if period := sb.Get(keyServicePeriod); period != nil { 212 service.ExpectationPeriod = database.BytesToUint64(period) 213 } 214 if maxevents := sb.Get(keyServiceMaxEvents); maxevents != nil { 215 service.MaxNumberEvents = database.BytesToUint64(maxevents) 216 } 217 } 218 219 // get returns the service structure associated with the []byte-formatted service ID 220 func get(id []byte) *Service { 221 var service Service 222 db := database.GetDB() 223 err := db.View(func(tx *bbolt.Tx) error { 224 225 // Get the root services bucket and the requested service bucket 226 var b, sb *bbolt.Bucket 227 if b = tx.Bucket(database.BucketKeyServices); b == nil { 228 log.Panic("The services bucket does not exist") 229 } 230 if sb = b.Bucket(id); sb == nil { 231 return errors.New("no service found for the given id") 232 } 233 234 // Get service information from the bucket 235 service.fromBucket(id, sb) 236 return nil 237 }) 238 if err != nil { 239 log.Println(err) 240 return nil 241 } 242 return &service 243 } 244 245 // getIDFromToken looks up the given token in the service-tokens bucket and returns 246 // the ID if it's found, otherwise returning nil 247 func getIDFromToken(token string) []byte { 248 var id []byte 249 db := database.GetDB() 250 db.View(func(tx *bbolt.Tx) error { 251 stb := tx.Bucket(database.BucketKeyServiceTokens) 252 if stb == nil { 253 log.Panic("The service-tokens bucket does not exist") 254 } 255 id = stb.Get([]byte(token)) 256 return nil 257 }) 258 return id 259 }