sermoni

"Service monitor" / cronjob status service
Log | Files | Refs

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 }