Add depot, attendant, jetfire, optimus, ota services with kustomize overlays
This commit is contained in:
29
services/attendant/services/config.go
Normal file
29
services/attendant/services/config.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
|
||||
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
configOnce sync.Once
|
||||
configInstance vconfig.ConfigServiceInterface
|
||||
)
|
||||
|
||||
func GetVehicleConfig() vconfig.ConfigServiceInterface {
|
||||
configOnce.Do(func() {
|
||||
if configInstance != nil {
|
||||
return
|
||||
}
|
||||
logger.Info().Msg("init vehicle config instance")
|
||||
configInstance = vconfig.NewConfigService()
|
||||
})
|
||||
return configInstance
|
||||
}
|
||||
|
||||
func SetVehicleConfig(c vconfig.ConfigServiceInterface) {
|
||||
configInstance = c
|
||||
}
|
||||
278
services/attendant/services/db.go
Normal file
278
services/attendant/services/db.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/db"
|
||||
q "github.com/fiskerinc/cloud-services/pkg/db/queries"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
dbOnce sync.Once
|
||||
dbInstance *DB
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
client *db.DBClient
|
||||
cars q.CarsInterface
|
||||
carVersionsLog q.CarVersionsLogInterface
|
||||
carupdates q.CarUpdatesInterface
|
||||
filekeys q.FileKeysInterface
|
||||
manifests q.UpdateManifestsInterface
|
||||
ecu q.ECUInterface
|
||||
eccKeys q.EccKeysInterface
|
||||
ratePlan q.RatePlanInterface
|
||||
updateManifestSUMSVersions q.SUMSVersionsInterface
|
||||
carConfigData q.CarConfigDataInterface
|
||||
|
||||
onceEcu sync.Once
|
||||
onceClient sync.Once
|
||||
onceCars sync.Once
|
||||
onceCarVersionsLog sync.Once
|
||||
onceCarUpdates sync.Once
|
||||
onceFileKeys sync.Once
|
||||
onceManifests sync.Once
|
||||
onceEccKeys sync.Once
|
||||
onceRatePlan sync.Once
|
||||
onceUpdateManifestSUMSVersions sync.Once
|
||||
onceCarConfigData sync.Once
|
||||
|
||||
}
|
||||
|
||||
func GetDB() *DB {
|
||||
dbOnce.Do(func() {
|
||||
if dbInstance != nil {
|
||||
return
|
||||
}
|
||||
logger.Info().Msg("init DB instance")
|
||||
dbInstance = &DB{}
|
||||
})
|
||||
return dbInstance
|
||||
}
|
||||
|
||||
func SetDB(db *DB) {
|
||||
if dbInstance != nil {
|
||||
dbInstance.Close()
|
||||
}
|
||||
dbInstance = db
|
||||
}
|
||||
|
||||
func (d *DB) GetDBClient() *db.DBClient {
|
||||
d.onceClient.Do(func() {
|
||||
if d.client != nil {
|
||||
return
|
||||
}
|
||||
logger.Info().Msg("init DBClient instance")
|
||||
client := &db.DBClient{}
|
||||
client.RegisterManyToManyRel([]interface{}{
|
||||
(*common.CarToDriver)(nil),
|
||||
})
|
||||
err := client.InitSchema([]interface{}{
|
||||
(*common.UpdateManifest)(nil),
|
||||
(*common.Car)(nil),
|
||||
(*common.CarToDriver)(nil),
|
||||
(*common.CarUpdateStatus)(nil),
|
||||
(*common.CarUpdate)(nil),
|
||||
(*common.FileKey)(nil),
|
||||
(*common.RatePlanTMobile)(nil),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
// Uncomment below to show generated SQL queries
|
||||
// client.GetConn().AddQueryHook(db.SQLLogger{})
|
||||
d.client = client
|
||||
})
|
||||
return d.client
|
||||
}
|
||||
|
||||
func (d *DB) SetDBClient(client *db.DBClient) {
|
||||
if d.client != nil {
|
||||
d.client.Close()
|
||||
}
|
||||
d.client = client
|
||||
}
|
||||
|
||||
func (d *DB) Close() {
|
||||
if d.client == nil {
|
||||
return
|
||||
}
|
||||
d.client.Close()
|
||||
}
|
||||
|
||||
func (d *DB) GetCarUpdates() q.CarUpdatesInterface {
|
||||
d.onceCarUpdates.Do(func() {
|
||||
if d.carupdates != nil {
|
||||
return
|
||||
}
|
||||
instance := &q.CarUpdates{}
|
||||
instance.SetClient(d.GetDBClient())
|
||||
d.carupdates = instance
|
||||
})
|
||||
return d.carupdates
|
||||
}
|
||||
|
||||
func (d *DB) SetCarUpdates(carupdates q.CarUpdatesInterface) {
|
||||
d.carupdates = carupdates
|
||||
}
|
||||
|
||||
func (d *DB) GetCars() q.CarsInterface {
|
||||
d.onceCars.Do(func() {
|
||||
if d.cars != nil {
|
||||
return
|
||||
}
|
||||
instance := &q.Cars{}
|
||||
instance.SetClient(d.GetDBClient())
|
||||
d.cars = instance
|
||||
})
|
||||
return d.cars
|
||||
}
|
||||
|
||||
func (d *DB) SetCars(cars q.CarsInterface) {
|
||||
d.cars = cars
|
||||
}
|
||||
|
||||
func (d *DB) GetCarVersionsLog() q.CarVersionsLogInterface {
|
||||
d.onceCarVersionsLog.Do(func() {
|
||||
if d.carVersionsLog != nil {
|
||||
return
|
||||
}
|
||||
instance := &q.CarVersionsLog{}
|
||||
instance.SetClient(d.GetDBClient())
|
||||
d.carVersionsLog = instance
|
||||
})
|
||||
return d.carVersionsLog
|
||||
}
|
||||
|
||||
func (d *DB) SetCarVersionsLog(log q.CarVersionsLogInterface) {
|
||||
d.carVersionsLog = log
|
||||
}
|
||||
|
||||
func (d *DB) GetFileKeys() q.FileKeysInterface {
|
||||
d.onceFileKeys.Do(func() {
|
||||
if d.filekeys != nil {
|
||||
return
|
||||
}
|
||||
instance := &q.FileKeys{}
|
||||
instance.SetClient(d.GetDBClient())
|
||||
d.filekeys = instance
|
||||
})
|
||||
return d.filekeys
|
||||
}
|
||||
|
||||
func (d *DB) SetFileKeys(filekeys q.FileKeysInterface) {
|
||||
d.filekeys = filekeys
|
||||
}
|
||||
|
||||
func (d *DB) GetUpdateManifests() q.UpdateManifestsInterface {
|
||||
d.onceManifests.Do(func() {
|
||||
if d.manifests != nil {
|
||||
return
|
||||
}
|
||||
instance := q.NewUpdateManifest(nil)
|
||||
instance.SetClient(d.GetDBClient())
|
||||
d.manifests = instance
|
||||
})
|
||||
return d.manifests
|
||||
}
|
||||
func (d *DB) GetECU() q.ECUInterface {
|
||||
|
||||
d.onceEcu.Do(func() {
|
||||
if d.ecu != nil {
|
||||
return
|
||||
}
|
||||
instance := &q.ECU{}
|
||||
instance.SetClient(d.GetDBClient())
|
||||
d.ecu = instance
|
||||
})
|
||||
return d.ecu
|
||||
|
||||
}
|
||||
|
||||
func (d *DB) SetECU(instance q.ECUInterface) {
|
||||
d.ecu = instance
|
||||
}
|
||||
|
||||
func (d *DB) SetManifests(manifests q.UpdateManifestsInterface) {
|
||||
d.manifests = manifests
|
||||
}
|
||||
|
||||
func (d *DB) ModifyUpdateStatus(id int, status string) (*common.CarUpdate, error) {
|
||||
cu := d.GetCarUpdates()
|
||||
updates, err := cu.SelectByID(int64(id))
|
||||
if err != nil {
|
||||
return updates, err
|
||||
}
|
||||
|
||||
updates.Status = status
|
||||
_, err = cu.UpdateStatus(updates)
|
||||
|
||||
return updates, err
|
||||
}
|
||||
|
||||
func (d *DB) GetECCKeys() q.EccKeysInterface {
|
||||
d.onceEccKeys.Do(func() {
|
||||
if d.eccKeys != nil {
|
||||
return
|
||||
}
|
||||
eccKeys := &q.EccKeys{}
|
||||
eccKeys.SetClient(d.GetDBClient())
|
||||
d.eccKeys = eccKeys
|
||||
})
|
||||
return d.eccKeys
|
||||
}
|
||||
|
||||
func (d *DB) SetECCKeys(eccKeys q.EccKeysInterface) {
|
||||
d.eccKeys = eccKeys
|
||||
}
|
||||
|
||||
func (d *DB) GetRatePlan() q.RatePlanInterface {
|
||||
d.onceRatePlan.Do(func() {
|
||||
if d.ratePlan != nil {
|
||||
return
|
||||
}
|
||||
instance := &q.RatePlanTmobile{}
|
||||
instance.SetClient(d.GetDBClient())
|
||||
d.ratePlan = instance
|
||||
})
|
||||
return d.ratePlan
|
||||
}
|
||||
|
||||
func (d *DB) SetRatePlan(ratePlan q.RatePlanInterface) {
|
||||
d.ratePlan = ratePlan
|
||||
}
|
||||
|
||||
func (d *DB) GetUpdateManifestSUMSVersions() q.SUMSVersionsInterface {
|
||||
d.onceUpdateManifestSUMSVersions.Do(func() {
|
||||
if d.updateManifestSUMSVersions != nil {
|
||||
return
|
||||
}
|
||||
instance := &q.SUMSVersions{}
|
||||
instance.SetClient(d.GetDBClient())
|
||||
d.updateManifestSUMSVersions = instance
|
||||
})
|
||||
return d.updateManifestSUMSVersions
|
||||
}
|
||||
|
||||
func (d *DB) SetUpdateManifestVersions(umv q.SUMSVersionsInterface) {
|
||||
d.updateManifestSUMSVersions = umv
|
||||
}
|
||||
|
||||
func (d *DB) GetCarConfigData() q.CarConfigDataInterface {
|
||||
d.onceCarConfigData.Do(func() {
|
||||
if d.carConfigData != nil {
|
||||
return
|
||||
}
|
||||
logger.Debug().Msg("Init CarConfigData instance")
|
||||
carConfigData := &q.CarConfigData{}
|
||||
carConfigData.SetClient(d.GetDBClient())
|
||||
d.carConfigData = carConfigData
|
||||
})
|
||||
return d.carConfigData
|
||||
}
|
||||
|
||||
func (d *DB) SetCarConfigData(carConfigData q.CarConfigDataInterface) {
|
||||
d.carConfigData = carConfigData
|
||||
}
|
||||
20
services/attendant/services/dbc.go
Normal file
20
services/attendant/services/dbc.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/dbc"
|
||||
"github.com/fiskerinc/cloud-services/pkg/dbc/models"
|
||||
"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
|
||||
)
|
||||
|
||||
var model models.DBCVersionInterface
|
||||
var collectionOnce sync.Once
|
||||
|
||||
// GetDBCCollection returns singleton instance of collection of DBCs
|
||||
func GetDBC() *descriptor.Database {
|
||||
collectionOnce.Do(func() {
|
||||
model = dbc.NewFM29_FRSD390_DBC()
|
||||
})
|
||||
return model.GetDatabase()
|
||||
}
|
||||
24
services/attendant/services/dtc_cache.go
Normal file
24
services/attendant/services/dtc_cache.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||
)
|
||||
|
||||
var (
|
||||
MAX_KEY_CACHE int = envtool.GetEnvInt("MAX_KEY_CACHE", 10000)
|
||||
carDtcsCache cache.CarDTCsCacheInterface
|
||||
onceDTCCache sync.Once
|
||||
)
|
||||
|
||||
func GetCarDtcCache() cache.CarDTCsCacheInterface {
|
||||
onceDTCCache.Do(func() {
|
||||
if carDtcsCache == nil {
|
||||
carDtcsCache = cache.NewCarDTCsCache(MAX_KEY_CACHE)
|
||||
}
|
||||
})
|
||||
|
||||
return carDtcsCache
|
||||
}
|
||||
28
services/attendant/services/ecu_cache.go
Normal file
28
services/attendant/services/ecu_cache.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/cache"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||
)
|
||||
|
||||
var (
|
||||
MAX_ECU_KEY_CACHE int = envtool.GetEnvInt("MAX_ECU_KEY_CACHE", 10000)
|
||||
ring cache.RingMapInterface
|
||||
onceECUCache sync.Once
|
||||
)
|
||||
|
||||
func GetCarEcuCache() cache.RingMapInterface {
|
||||
onceECUCache.Do(func() {
|
||||
if ring == nil {
|
||||
ring = cache.NewRingMap(MAX_ECU_KEY_CACHE)
|
||||
}
|
||||
})
|
||||
|
||||
return ring
|
||||
}
|
||||
|
||||
func SetCarEcuCache(value cache.RingMapInterface) {
|
||||
ring = value
|
||||
}
|
||||
88
services/attendant/services/foa.go
Normal file
88
services/attendant/services/foa.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
s "github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
|
||||
"github.com/fiskerinc/cloud-services/pkg/foa"
|
||||
"github.com/fiskerinc/cloud-services/pkg/httpclient"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||
)
|
||||
|
||||
var UPDATE_MANIFEST_IDS_TO_NOTIFY_FOA = []int64{816, 817, 818, 819, 820}
|
||||
|
||||
var (
|
||||
foaService FoaServiceInterface
|
||||
foaOnce sync.Once
|
||||
)
|
||||
|
||||
func GetFoaService() FoaServiceInterface {
|
||||
foaOnce.Do(func() {
|
||||
if foaService != nil {
|
||||
return
|
||||
}
|
||||
foaService = NewFoaService()
|
||||
})
|
||||
|
||||
return foaService
|
||||
}
|
||||
|
||||
func SetFoaService(foa FoaServiceInterface) {
|
||||
foaService = foa
|
||||
}
|
||||
|
||||
func NewFoaService() FoaServiceInterface {
|
||||
return &FoaService{
|
||||
foaURL: envtool.GetEnv("FOA_URL", "REPLACE_ME"),
|
||||
foaAPIToken: envtool.GetEnv("FOA_API_KEY", "REPLACE_ME"),
|
||||
}
|
||||
}
|
||||
|
||||
type FoaServiceInterface interface {
|
||||
OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error)
|
||||
}
|
||||
|
||||
type FoaService struct {
|
||||
foaURL string
|
||||
foaAPIToken string
|
||||
}
|
||||
|
||||
func (f *FoaService) OtaUpdateStatus(vin string, carUpdate *common.CarUpdate, status *common.CarUpdateProgress) (*http.Response, error) {
|
||||
if !slices.Contains(UPDATE_MANIFEST_IDS_TO_NOTIFY_FOA, carUpdate.UpdateManifestID) {
|
||||
// Nothing to send if the manifest is not one of the specified IDs
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var body interface{} = nil
|
||||
|
||||
switch status.Status {
|
||||
case s.ManifestSucceeded:
|
||||
body = foa.BuildOtaUpdateStatusSuccessRequest(vin, carUpdate.UpdateManifestID)
|
||||
case s.ManifestError:
|
||||
body = foa.BuildOtaUpdateStatusFailedRequest(vin, carUpdate.UpdateManifestID, status.Info)
|
||||
case s.ManifestCanceled:
|
||||
body = foa.BuildOtaUpdateStatusCanceledRequest(vin, carUpdate.UpdateManifestID, status.Info)
|
||||
}
|
||||
|
||||
if body == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
logger.Info().Msgf("Notifying FOA for %s of update %d status %s", vin, carUpdate.UpdateManifestID, status.Status)
|
||||
|
||||
urlString, err := url.JoinPath(f.foaURL, "ota/update_status")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
postHeader := http.Header{}
|
||||
postHeader.Add("Authorization", "Bearer "+f.foaAPIToken)
|
||||
postHeader.Add("Content-Type", "application/json")
|
||||
|
||||
return httpclient.Post(urlString, body, postHeader)
|
||||
}
|
||||
62
services/attendant/services/kafka.go
Normal file
62
services/attendant/services/kafka.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/kafka"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
)
|
||||
|
||||
const serviceName = "attendant"
|
||||
const oldServiceName = "old-attendant"
|
||||
|
||||
var consumer, oldConsumer kafka.ConsumerInterface
|
||||
var consumerOnce sync.Once
|
||||
|
||||
// GetKafkaConsumer returns singleton instance of kafka consumer
|
||||
func GetKafkaConsumer() (kafka.ConsumerInterface, kafka.ConsumerInterface, error) {
|
||||
var err error
|
||||
|
||||
consumerOnce.Do(func() {
|
||||
consumer, err = kafka.NewConsumer(serviceName)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
oldConsumer, err = kafka.NewConsumer(oldServiceName)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return consumer, oldConsumer, nil
|
||||
}
|
||||
|
||||
var producer kafka.ProducerInterface
|
||||
var producerOnce sync.Once
|
||||
|
||||
func GetKafkaProducer() (kafka.ProducerInterface, error) {
|
||||
var err error
|
||||
producerOnce.Do(func() {
|
||||
if producer == nil {
|
||||
var producerTemp kafka.ProducerInterface
|
||||
producerTemp, err = kafka.NewProducer(context.Background())
|
||||
if err != nil {
|
||||
logger.Err(err).Send()
|
||||
}
|
||||
producer = producerTemp
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return producer, err
|
||||
}
|
||||
|
||||
func SetKafkaProducer(k kafka.ProducerInterface) {
|
||||
producer = k
|
||||
}
|
||||
215
services/attendant/services/keep_awake.go
Normal file
215
services/attendant/services/keep_awake.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/common"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// This can probably be moved into a better spot, but we are on a time limit
|
||||
var ADD_TIME = time.Minute
|
||||
|
||||
type KeepAwake struct {
|
||||
tick *time.Ticker
|
||||
KAI KeepAwakeInterface
|
||||
}
|
||||
|
||||
type KeepAwakeInterface interface {
|
||||
CheckKeepAwakeMessages()
|
||||
// addToKeepAwakeMessages(vin string) (err error)
|
||||
RemoveKeepAwakeMessage(vin string) (err error)
|
||||
// getKeepAwakeMessages() (vins []string, err error)
|
||||
SendFirstKeepAwakeMessage(vin string) (err error)
|
||||
// sendKeepAwakeMessage(vin string) (err error)
|
||||
}
|
||||
|
||||
type KeepAwakeImplementation struct {
|
||||
Cars map[string]time.Time // Map of car vins to the time it was last ran
|
||||
oneTime sync.Once
|
||||
mapLock sync.Mutex
|
||||
}
|
||||
|
||||
func NewKeepAwakeService() (ka *KeepAwake) {
|
||||
ka = &KeepAwake{}
|
||||
|
||||
ka.tick = time.NewTicker(time.Minute)
|
||||
ka.KAI = &KeepAwakeImplementation{}
|
||||
|
||||
// Start our timer
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ka.tick.C:
|
||||
ka.KAI.CheckKeepAwakeMessages()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (ka *KeepAwake) SetService(inf KeepAwakeInterface) {
|
||||
ka.KAI = inf
|
||||
}
|
||||
|
||||
func (ka *KeepAwake) RemoveKeepAwakeMessage(vin string) (err error) {
|
||||
return ka.KAI.RemoveKeepAwakeMessage(vin)
|
||||
}
|
||||
|
||||
func (ka *KeepAwake) SendFirstKeepAwakeMessage(vin string) (err error) {
|
||||
return ka.KAI.SendFirstKeepAwakeMessage(vin)
|
||||
}
|
||||
|
||||
// Need to move this out of here, as this manifest sender is created every time a manifest is to be sent
|
||||
// On a timeout, check if the keep awake messages need to be sent
|
||||
func (k *KeepAwakeImplementation) CheckKeepAwakeMessages() {
|
||||
k.oneTime.Do(func() {
|
||||
k.Cars = make(map[string]time.Time)
|
||||
})
|
||||
|
||||
k.mapLock.Lock()
|
||||
vins, _ := k.getKeepAwakeMessages()
|
||||
|
||||
for _, vin := range vins {
|
||||
k.sendKeepAwakeMessage(vin)
|
||||
|
||||
k.addToKeepAwakeMessages(vin)
|
||||
}
|
||||
k.mapLock.Unlock()
|
||||
// Call this function again in some amount of time
|
||||
}
|
||||
|
||||
func (k *KeepAwakeImplementation) addToKeepAwakeMessages(vin string) (err error) {
|
||||
k.Cars[vin] = time.Now().Add(ADD_TIME)
|
||||
return
|
||||
}
|
||||
|
||||
func (k *KeepAwakeImplementation) RemoveKeepAwakeMessage(vin string) (err error) {
|
||||
k.oneTime.Do(func() {
|
||||
k.Cars = make(map[string]time.Time)
|
||||
})
|
||||
|
||||
// Remove from Redis
|
||||
err = k.removeRedisCarEntry(vin)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("Unable to remove keeep awake message from redis")
|
||||
}
|
||||
|
||||
logger.Info().Msgf("Ended keep awake messaging for %s", vin)
|
||||
k.mapLock.Lock()
|
||||
defer k.mapLock.Unlock()
|
||||
_, ok := k.Cars[vin]
|
||||
if !ok {
|
||||
logger.Debug().Msgf("Attempted to end keep awake for %s, but not in list", vin)
|
||||
}
|
||||
delete(k.Cars, vin)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// This is a list of vins that need sendKeepAwakeMessage
|
||||
func (k *KeepAwakeImplementation) getKeepAwakeMessages() (vins []string, err error) {
|
||||
for vin, t := range k.Cars {
|
||||
// Parse out the time and check if its before the time it is now
|
||||
if t.Before(time.Now()) {
|
||||
// This needs processing
|
||||
vins = append(vins, vin)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (k *KeepAwakeImplementation) SendFirstKeepAwakeMessage(vin string) (err error) {
|
||||
k.oneTime.Do(func() {
|
||||
k.Cars = make(map[string]time.Time)
|
||||
})
|
||||
|
||||
// Additionally add to the list of redis
|
||||
err = k.addRedisCarEntry(vin)
|
||||
logger.Err(err).Msg("")
|
||||
|
||||
logger.Info().Msgf("Started keep awake messaging for %s", vin)
|
||||
k.mapLock.Lock()
|
||||
defer k.mapLock.Unlock()
|
||||
err = k.sendKeepAwakeMessage(vin)
|
||||
k.addToKeepAwakeMessages(vin)
|
||||
return err
|
||||
}
|
||||
|
||||
// Send a special message to keep the tbox awake to download the update
|
||||
// We want to continue to call this until the download is complete or if we fail
|
||||
func (k *KeepAwakeImplementation) sendKeepAwakeMessage(vin string) (err error) {
|
||||
client := RedisClientPool().GetFromPool()
|
||||
defer client.Close()
|
||||
|
||||
// check if in redis
|
||||
found, err := k.checkRedisForCarEntry(vin, client)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// If we did not find the redis entry, then another service has removed it from the keep alive set
|
||||
// Don't call k.Delete as it creates a lock on the map, and then we will be deadlocked
|
||||
if !found {
|
||||
delete(k.Cars, vin)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info().Msgf("sending keep awake message for %s", vin)
|
||||
|
||||
type Action struct {
|
||||
Action string `json:"action"`
|
||||
Timeout int32 `json:"timeout"`
|
||||
}
|
||||
logger.Debug().Msgf("Sending redis queue- %s, key- %s, hander- %s, data- %v", "attendant", vin, "can_network", Action{Action: "on"})
|
||||
err = client.SafeQueueMessage(common.TRex.Key(vin), common.Message{
|
||||
Handler: "can_network",
|
||||
Data: Action{Action: "on", Timeout: 120},
|
||||
})
|
||||
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (k *KeepAwakeImplementation) checkRedisForCarEntry(vin string, client redis.Client) (found bool, err error) {
|
||||
line, err := client.Execute("SISMEMBER", "can_keep_awake", vin)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
check, ok := line.(int64)
|
||||
if !ok {
|
||||
err = fmt.Errorf("received wrong type from redis for the can_keep_awake for %s, %v, %s", vin, line, reflect.TypeOf(line))
|
||||
}
|
||||
return check > 0, err
|
||||
}
|
||||
|
||||
func (k *KeepAwakeImplementation) removeRedisCarEntry(vin string) (err error) {
|
||||
client := RedisClientPool().GetFromPool()
|
||||
defer client.Close()
|
||||
_, err = client.Execute("SREM", "can_keep_awake", vin)
|
||||
return
|
||||
}
|
||||
|
||||
func (k *KeepAwakeImplementation) addRedisCarEntry(vin string) (err error) {
|
||||
client := RedisClientPool().GetFromPool()
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.Execute("SADD", "can_keep_awake", vin)
|
||||
return
|
||||
}
|
||||
|
||||
type MockKeepAwakeImplementation struct{}
|
||||
|
||||
func (k *MockKeepAwakeImplementation) CheckKeepAwakeMessages() {}
|
||||
|
||||
func (k *MockKeepAwakeImplementation) RemoveKeepAwakeMessage(vin string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (k *MockKeepAwakeImplementation) SendFirstKeepAwakeMessage(vin string) (err error) {
|
||||
return
|
||||
}
|
||||
68
services/attendant/services/keep_awake_test.go
Normal file
68
services/attendant/services/keep_awake_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestKeepAwake(t *testing.T){
|
||||
t.Skip()
|
||||
ADD_TIME = 0
|
||||
ka := KeepAwake{}
|
||||
impl := &KeepAwakeImplementation{}
|
||||
ka.SetService(impl)
|
||||
|
||||
testVin := "JH4KA7680RC01"
|
||||
// Check that trying to delete a non-existing set is okay
|
||||
err := impl.RemoveKeepAwakeMessage(testVin)
|
||||
if err != nil{
|
||||
t.Error(err)
|
||||
}
|
||||
client := RedisClientPool().GetFromPool()
|
||||
found, err := impl.checkRedisForCarEntry(testVin, client)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if found{
|
||||
t.Error("did find expected vin when not supposed to")
|
||||
}
|
||||
client.Close()
|
||||
|
||||
// Check we can successfully add
|
||||
err = impl.SendFirstKeepAwakeMessage(testVin)
|
||||
if err != nil{
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
client = RedisClientPool().GetFromPool()
|
||||
found, err = impl.checkRedisForCarEntry(testVin, client)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !found{
|
||||
t.Error("did not find expected vin")
|
||||
}
|
||||
client.Close()
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
vins, err := impl.getKeepAwakeMessages()
|
||||
if err != nil{
|
||||
t.Error(err)
|
||||
}
|
||||
if len(vins) != 1{
|
||||
t.Error("len of vins wrong ", len(vins))
|
||||
}
|
||||
|
||||
err = impl.RemoveKeepAwakeMessage(testVin)
|
||||
if err != nil{
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
vins, err = impl.getKeepAwakeMessages()
|
||||
if err != nil{
|
||||
t.Error(err)
|
||||
}
|
||||
if len(vins) != 0{
|
||||
t.Error("len of vins wrong ", len(vins))
|
||||
}
|
||||
}
|
||||
32
services/attendant/services/mongo.go
Normal file
32
services/attendant/services/mongo.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/mongo"
|
||||
)
|
||||
|
||||
var (
|
||||
clientOnce sync.Once
|
||||
client mongo.Client
|
||||
)
|
||||
|
||||
// GetMongoClient returns singleton instance of mongo client
|
||||
func GetMongoClient() (mongo.Client, error) {
|
||||
var err error
|
||||
clientOnce.Do(func() {
|
||||
client, err = initMongoClient()
|
||||
})
|
||||
|
||||
return client, err
|
||||
}
|
||||
|
||||
func initMongoClient() (mongo.Client, error) {
|
||||
var err error
|
||||
|
||||
if client == nil {
|
||||
client, err = mongo.NewClient(mongo.StandardDB)
|
||||
}
|
||||
|
||||
return client, err
|
||||
}
|
||||
28
services/attendant/services/redis.go
Normal file
28
services/attendant/services/redis.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
clientPoolOnce sync.Once
|
||||
clientPool redis.ClientPoolInterface
|
||||
)
|
||||
|
||||
func RedisClientPool() redis.ClientPoolInterface {
|
||||
clientPoolOnce.Do(func() {
|
||||
if clientPool != nil {
|
||||
return
|
||||
}
|
||||
|
||||
clientPool = redis.NewClientPool()
|
||||
})
|
||||
|
||||
return clientPool
|
||||
}
|
||||
|
||||
func SetRedisClientPool(cp redis.ClientPoolInterface) {
|
||||
clientPool = cp
|
||||
}
|
||||
28
services/attendant/services/sap.go
Normal file
28
services/attendant/services/sap.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
sapService vconfig.SAPServiceInterface
|
||||
sapOnce sync.Once
|
||||
)
|
||||
|
||||
func GetSapService() vconfig.SAPServiceInterface {
|
||||
sapOnce.Do(func() {
|
||||
if sapService != nil {
|
||||
return
|
||||
}
|
||||
sapService = vconfig.NewSAPService()
|
||||
})
|
||||
|
||||
return sapService
|
||||
}
|
||||
|
||||
// SetSapService is supposed t be used for testing.
|
||||
func SetSapService(sap vconfig.SAPServiceInterface) {
|
||||
sapService = sap
|
||||
}
|
||||
43
services/attendant/services/sms.go
Normal file
43
services/attendant/services/sms.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
|
||||
"github.com/fiskerinc/cloud-services/pkg/logger"
|
||||
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
var smsClient sms.SMSServiceClient
|
||||
var smsClientOnce sync.Once
|
||||
|
||||
func newSmsClient() {
|
||||
logger.Info().Msg("Init SMS client")
|
||||
target := fmt.Sprintf("%s:%s",
|
||||
envtool.GetEnv("SMS_HOST", "sms"),
|
||||
envtool.GetEnv("SMS_PORT", "8077"))
|
||||
c, err := grpc.Dial(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
|
||||
smsClient = sms.NewSMSServiceClient(c)
|
||||
}
|
||||
|
||||
func GetSMSClient() sms.SMSServiceClient {
|
||||
smsClientOnce.Do(func() {
|
||||
if smsClient != nil {
|
||||
return
|
||||
}
|
||||
newSmsClient()
|
||||
})
|
||||
|
||||
return smsClient
|
||||
}
|
||||
|
||||
func SetSmsClient(c sms.SMSServiceClient) {
|
||||
smsClient = c
|
||||
}
|
||||
Reference in New Issue
Block a user