Files
cloud-services/pkg/db/queries/updatemanifests.go

567 lines
16 KiB
Go

package queries
import (
"fmt"
"fiskerinc.com/modules/common"
"fiskerinc.com/modules/db"
"fiskerinc.com/modules/logger"
"fiskerinc.com/modules/validator"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/pkg/errors"
)
type UpdateManifestsInterface interface {
QueryBaseInterface
Archive(ids []int64, active bool) (orm.Result, error)
Count(filter common.UpdateManifest) (int, error)
Delete(manifest *common.UpdateManifest) (orm.Result, error)
Insert(manifest *common.UpdateManifest) (orm.Result, error)
Select(filter *common.UpdateManifest, paging *PageQueryOptions) ([]common.UpdateManifest, error)
SelectByVIN(vin string, paging *PageQueryOptions) ([]common.StatusManifest, error)
Update(manifest *common.UpdateManifest) (orm.Result, error)
Load(manifest *common.UpdateManifest) error
Search(filter common.UpdateManifestSearch, paging *PageQueryOptions) ([]common.UpdateManifest, error)
SearchCount(filter common.UpdateManifestSearch) (int, error)
ECURollback(man *common.UpdateManifestECU, vin string) ([]*common.UpdateManifestECU, error)
ECUInsert(manifest *common.UpdateManifestECU) (orm.Result, error)
FileInsert(manifest *common.UpdateManifestFile) (orm.Result, error)
AddSUMSVersion(manifest *common.UpdateManifest) (orm.Result, error)
SelectFlashPackByVersion(versionNumber string) (manifest common.UpdateManifest, err error)
}
// NewUpdateManifest returns UpdateManifestInterface.
// mode allows to use more flexible requests.
// If mode is nil, default mode is used.
func NewUpdateManifest(mode UpdateManifestMode) UpdateManifestsInterface {
if mode == nil {
return &UpdateManifests{
mode: DefaultMode{},
}
}
return &UpdateManifests{
mode: mode,
}
}
type UpdateManifests struct {
mode UpdateManifestMode
QueryBase
}
func (um *UpdateManifests) Count(filter common.UpdateManifest) (int, error) {
query := um.GetDBConn().Model((*common.UpdateManifest)(nil))
um.selectFilter(query, &filter)
return query.Count()
}
func (um *UpdateManifests) Delete(manifest *common.UpdateManifest) (orm.Result, error) {
total := ORMResults{}
err := validator.ValidateIDField(manifest.ID)
if err != nil {
return nil, err
}
err = um.Load(manifest)
if err != nil {
return nil, err
}
tx, err := um.GetDBConn().Begin()
if err != nil {
return nil, err
}
_, err = um.manifestDelete(tx, &total, manifest)
if err != nil {
tx.Rollback()
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
return &total, err
}
func (um *UpdateManifests) Insert(manifest *common.UpdateManifest) (orm.Result, error) {
total := ORMResults{}
tx, err := um.GetDBConn().Begin()
if err != nil {
err = errors.WithStack(err)
return nil, err
}
_, err = um.manifestInsert(tx, &total, manifest)
if err != nil {
tx.Rollback()
err = errors.WithStack(err)
return nil, err
}
err = tx.Commit()
if err != nil {
err = errors.WithStack(err)
return nil, err
}
return &total, nil
}
func (um *UpdateManifests) ECUInsert(ecu *common.UpdateManifestECU) (orm.Result, error) {
total := ORMResults{}
tx, err := um.GetDBConn().Begin()
if err != nil {
return nil, err
}
_, err = um.ecuInsert(tx, &total, ecu)
if err != nil {
tx.Rollback()
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
return &total, nil
}
func (um *UpdateManifests) FileInsert(file *common.UpdateManifestFile) (orm.Result, error) {
total := ORMResults{}
tx, err := um.GetDBConn().Begin()
if err != nil {
return nil, err
}
_, err = um.fileInsert(tx, &total, file)
if err != nil {
tx.Rollback()
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
return &total, nil
}
func (um *UpdateManifests) Select(filter *common.UpdateManifest, paging *PageQueryOptions) ([]common.UpdateManifest, error) {
manifests := []common.UpdateManifest{}
query := um.GetDBConn().Model(&manifests)
um.selectFilter(query, filter)
um.pageQuery(query, paging)
err := query.Select()
return manifests, errors.WithStack(err)
}
func (um *UpdateManifests) Archive(ids []int64, active bool) (orm.Result, error) {
manifests := []common.UpdateManifest{}
return um.resultWithStack(
um.GetDBConn().Model(&manifests).
Set("active = ?", active).
Where("id IN (?)", pg.In(ids)).
Update(),
)
}
func (um *UpdateManifests) SelectByVIN(vin string, paging *PageQueryOptions) ([]common.StatusManifest, error) {
manifests := []common.StatusManifest{}
query := um.GetDBConn().Model(&manifests)
query.
ColumnExpr("status_manifest.*").
ColumnExpr("car_updates.status as status").
ColumnExpr("car_updates.updated_at as status_updated").
ColumnExpr("status_manifest.created_at as manifest_created").
Join("LEFT JOIN car_updates ON car_updates.update_manifest_id=status_manifest.id").
Where("vin = ?", vin)
query = um.mode.SelectByVINCondition(query)
query = um.pageQuery(query, paging)
err := query.Select()
return manifests, errors.WithStack(err)
}
func (um *UpdateManifests) Update(manifest *common.UpdateManifest) (orm.Result, error) {
err := validator.ValidateIDField(manifest.ID)
if err != nil {
return nil, err
}
query := um.GetDBConn().Model(manifest).Column(
"name",
"release_notes",
"type",
"fingerprint",
"rollback_enabled",
"update_duration",
"max_attempts",
)
if manifest.Active != nil {
query = query.Column("active")
}
if manifest.Env != "" {
query = query.Column("env")
}
if manifest.SUMS != "" {
query = query.Column("sums")
}
return query.WherePK().Update()
}
func (um *UpdateManifests) Load(manifest *common.UpdateManifest) error {
query := um.GetDBConn().Model(manifest)
if manifest.ID > 0 {
query.WherePK()
} else if manifest.Name != "" && manifest.Version != "" {
query.Where("name = ?name AND version = ?version")
} else {
return errors.New("no id, name or version")
}
// For Magna, we will add the manifest type, then we will only get it when it is equal to two
if manifest.ManifestType > 0 {
query.Where("manifest_type = ?", manifest.ManifestType)
}
if manifest.Env == "" {
manifest.Env = common.EnvCurrent
}
return um.mode.LoadRelations(query)
}
func (um *UpdateManifests) Search(filter common.UpdateManifestSearch, paging *PageQueryOptions) ([]common.UpdateManifest, error) {
manifests := []common.UpdateManifest{}
query := um.GetDBConn().Model(&manifests)
um.searchFilter(query, &filter)
um.pageQuery(query, paging)
err := query.Select()
return manifests, errors.WithStack(err)
}
func (um *UpdateManifests) SearchCount(filter common.UpdateManifestSearch) (int, error) {
query := um.GetDBConn().Model((*common.UpdateManifest)(nil))
um.searchFilter(query, &filter)
return query.Count()
}
// Used to get rollbacks for ecu's
func (um *UpdateManifests) ECURollback(man *common.UpdateManifestECU, vin string) (rollBackManifest []*common.UpdateManifestECU, err error) {
// Use our new rollback system, if it fails return the old get for rollbacks
// This may fail as the car_ecus system is a bit corrupted in data
targetedRollback, err := um.getECURollbackSpecificToCar(man, vin)
if err == nil && targetedRollback != nil {
rollBackManifest = append(rollBackManifest, targetedRollback)
return
}
return um.getRollbacksListOfPossible(man)
}
func (um *UpdateManifests) getECURollbackSpecificToCar(man *common.UpdateManifestECU, vin string) (rollbackManifest *common.UpdateManifestECU, err error) {
conn := um.GetDBConn()
targetCarECU := common.CarECU{}
targetCarECU.VIN = vin
targetCarECU.ECU = man.ECU
// Using the ecu and vin, get the latest carECU for that specific car
err = conn.Model(&targetCarECU).
Where("vin = ?", targetCarECU.VIN).
Where("ecu = ?", targetCarECU.ECU).
Where("version != 'unavailable'"). // We have so many of these though, like its not something that you can even roll back to
Order("epoch_usec DESC").
Limit(1).
Select()
if err != nil {
logger.Warn().Err(err).Msgf("failed to get car ecu for rollback for car %s ecu %s", vin, man.ECU)
return
}
if targetCarECU.Version == "" {
logger.Warn().Interface("target car ecu", targetCarECU).Msgf("failed to fetch a previous ecu version for car %s ecu %s", vin, man.ECU)
err = fmt.Errorf("getRollBackNew error")
return
}
targetManifest := common.UpdateManifestECU{}
err = conn.Model(&targetManifest).
Where("version = ?", targetCarECU.Version).
Where("ecu = ?", targetCarECU.ECU).
Select()
if err != nil {
logger.Warn().Err(err).Msgf("failed to get ecu rollback update manifest ecu for car %s", vin)
return
}
if targetManifest.ID == 0 {
logger.Warn().Interface("target ecu", targetCarECU).Msgf("failed to get ecu rollback update manifest ecu for car %s", vin)
err = fmt.Errorf("getRollBackNew error")
return
}
return
}
func (um *UpdateManifests) getRollbacksListOfPossible(man *common.UpdateManifestECU) (rollBackManifest []*common.UpdateManifestECU, err error) {
var manifests []*common.UpdateManifestECU
conn := um.GetDBConn()
sub := conn.Model(&manifests).ColumnExpr("version, max(created_at)").
Where("ecu = ?", man.ECU).
Where("hw_versions = ?", pg.Array(man.HWVersions)).
Where("version != ?", man.Version).
Group("version")
err = conn.
Model(&manifests).
With("sub", sub).
Column("update_manifest_ecu.version", "id").
Join("INNER JOIN sub ON update_manifest_ecu.version = sub.version AND "+
"update_manifest_ecu.created_at = sub.max").
Where("ecu = ?", man.ECU).
Where("hw_versions = ?", pg.Array(man.HWVersions)).
Where("update_manifest_ecu.version != ?", man.Version).
Relation("Files").
Relation("Files.WriteRegion").
Relation("Files.EraseRegion").
Order("version").
Select()
if err != nil {
errors.WithMessagef(err, "failed to do ECURollback() for manifest_id %d on ECU %s id: %d", man.UpdateManifestID, man.ECU, man.ID)
return nil, err
}
return
}
func (um *UpdateManifests) manifestDelete(operation db.Operation, resultsTotal *ORMResults, manifest *common.UpdateManifest) (orm.Result, error) {
// Removed manifest.ecuDelete as the deletes are now propagated by the database
result, err := um.resultWithStack(operation.Model(manifest).WherePK().Delete())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
return resultsTotal, nil
}
func (um *UpdateManifests) manifestInsert(operation db.Operation, resultsTotal *ORMResults, manifest *common.UpdateManifest) (orm.Result, error) {
result, err := um.resultWithStack(operation.Model(manifest).Insert())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
resultsTotal.SetModel(result.Model())
if manifest.ECUs != nil {
for i := range manifest.ECUs {
ecu := manifest.ECUs[i]
ecu.UpdateManifestID = manifest.ID
result, err = um.ecuInsert(operation, resultsTotal, ecu)
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
}
}
return resultsTotal, nil
}
func (um *UpdateManifests) ecuDelete(operation db.Operation, resultsTotal *ORMResults, ecu *common.UpdateManifestECU) (orm.Result, error) {
if ecu.Files != nil {
for i := range ecu.Files {
file := ecu.Files[i]
result, err := um.fileDelete(operation, resultsTotal, file)
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
}
}
result, err := operation.Model(ecu).WherePK().Delete()
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
return resultsTotal, nil
}
func (um *UpdateManifests) ecuInsert(operation db.Operation, resultsTotal *ORMResults, ecu *common.UpdateManifestECU) (orm.Result, error) {
result, err := um.resultWithStack(operation.Model(ecu).Insert())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
if ecu.Files != nil {
for i := range ecu.Files {
file := ecu.Files[i]
file.UpdateManifestECUID = ecu.ID
result, err := um.fileInsert(operation, resultsTotal, file)
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
}
}
return resultsTotal, nil
}
func (um *UpdateManifests) fileDelete(operation db.Operation, resultsTotal *ORMResults, file *common.UpdateManifestFile) (orm.Result, error) {
result, err := um.resultWithStack(operation.Model(file).WherePK().Delete())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
resultsTotal.SetModel(result.Model())
result, err = um.resultWithStack(operation.Model(&common.MemoryRegion{ID: file.WriteRegionID}).WherePK().Delete())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
if file.EraseRegionID != 0 {
result, err = um.resultWithStack(operation.Model(&common.MemoryRegion{ID: file.EraseRegionID}).WherePK().Delete())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
}
result, err = um.fileKeyDelete(operation, resultsTotal, file)
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
return resultsTotal, nil
}
func (um *UpdateManifests) fileKeyDelete(operation db.Operation, resultsTotal *ORMResults, file *common.UpdateManifestFile) (orm.Result, error) {
fk := &common.FileKey{FileID: file.FileID}
result, err := um.resultWithStack(operation.Model(fk).WherePK().Delete())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
return resultsTotal, err
}
func (um *UpdateManifests) fileInsert(operation db.Operation, resultsTotal *ORMResults, file *common.UpdateManifestFile) (orm.Result, error) {
result, err := um.resultWithStack(operation.Model(&file.WriteRegion).Insert())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
file.WriteRegionID = file.WriteRegion.ID
if file.EraseRegion != nil {
result, err = um.resultWithStack(operation.Model(file.EraseRegion).Insert())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
file.EraseRegionID = file.EraseRegion.ID
}
result, err = um.resultWithStack(operation.Model(file).Insert())
if um.hasErrorResult(resultsTotal, result, err) {
return nil, err
}
resultsTotal.SetModel(result.Model())
return resultsTotal, nil
}
func (um *UpdateManifests) selectFilter(query *orm.Query, filter *common.UpdateManifest) {
if filter.ID > 0 {
query.Where("id = ?", filter.ID)
}
if filter.Name != "" {
query.Where("name = ?", filter.Name)
}
if filter.Version != "" {
query.Where("version = ?", filter.Version)
}
if filter.Description != "" {
query.Where("description LIKE ?", filter.Description)
}
if filter.ManifestType > 0 {
query.Where("manifest_type = ?", filter.ManifestType)
}
if filter.Active != nil {
query.Where("active = ?", filter.Active)
}
if filter.Country != "" {
query.Where("country = ? or country is null", filter.Country)
}
if filter.PowerTrain != "" {
query.Where("powertrain = ? or powertrain is null", filter.PowerTrain)
}
if filter.Restraint != "" {
query.Where("restraint = ? or restraint is null", filter.Restraint)
}
if filter.Model != "" {
query.Where("model = ? or model is null", filter.Model)
}
if filter.Trim != "" {
query.Where("trim = ? or trim is null", filter.Trim)
}
if filter.Year != 0 {
query.Where("year = ? or year is null", filter.Year)
}
if filter.BodyType != "" {
query.Where("body_type = ? or body_type is null", filter.BodyType)
}
}
func (um *UpdateManifests) searchFilter(query *orm.Query, filter *common.UpdateManifestSearch) {
if filter.Search != "" {
query.Where("document @@ plainto_tsquery(?)", filter.Search)
}
if filter.ManifestType == common.SoftwareUpdateType {
filter.ManifestType = 0
query.WhereInMulti("manifest_type in (?)", common.SoftwareUpdateType, common.MagnaManifestUpdateType, common.AftersalesUpdateType)
}
um.selectFilter(query, &filter.UpdateManifest)
}
func (um *UpdateManifests) AddSUMSVersion(manifest *common.UpdateManifest) (orm.Result, error) {
return um.resultWithStack(um.GetDBConn().Model(manifest).Column("sums").Where("id = ?id AND sums IS NULL").Update())
}
// Given a flashpack version number: 14.4, we will return the corresponding flashpack
func (um *UpdateManifests) SelectFlashPackByVersion(versionNumber string) (manifest common.UpdateManifest, err error) {
// Prepare for Like Statement
versionNumber = "%" + versionNumber
err = um.GetDBConn().Model(&manifest).Where("manifest_type = ? AND name LIKE ?", common.AsBuiltUpdateType, versionNumber).Limit(1).Select()
return
}