blob: 101fbff91e0cb3555c2039fe3d12e5d729433598 [file] [edit]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package deviceconfig
import (
"context"
"strings"
"time"
"github.com/golang/protobuf/proto"
"go.chromium.org/chromiumos/infra/proto/go/device"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
"go.chromium.org/luci/common/proto/gitiles"
"go.chromium.org/luci/gae/service/datastore"
"go.chromium.org/infra/cros/lab_inventory/cfg2datastore"
"go.chromium.org/infra/libs/git"
)
const entityKind = "DevConfig"
// GetDeviceConfigIDStr returns a string as device config short name.
func GetDeviceConfigIDStr(cfgid *device.ConfigId) string {
// TODO (guocb) Add `BranchID` as part of DeviceConfigID.
var platformID, modelID, variantID string
if v := cfgid.GetPlatformId(); v != nil {
platformID = strings.ToLower(v.GetValue())
}
if v := cfgid.GetModelId(); v != nil {
modelID = strings.ToLower(v.GetValue())
}
if v := cfgid.GetVariantId(); v != nil {
variantID = strings.ToLower(v.GetValue())
}
return strings.Join([]string{platformID, modelID, variantID}, ".")
}
type devcfgEntity struct {
_kind string `gae:"$kind,DevConfig"`
ID string `gae:"$id"`
DevConfig []byte `gae:",noindex"`
Updated time.Time
}
func (e *devcfgEntity) SetUpdatedTime(t time.Time) {
e.Updated = t
}
func (e *devcfgEntity) GetMessagePayload() (proto.Message, error) {
cfg := device.Config{}
err := proto.Unmarshal(e.DevConfig, &cfg)
if err != nil {
return nil, err
}
return &cfg, nil
}
func (e *devcfgEntity) GetID() string {
return e.ID
}
func newDevCfgEntity(msg proto.Message) (cfg2datastore.EntityInterface, error) {
cfgData, err := proto.Marshal(msg)
if err != nil {
return nil, err
}
return &devcfgEntity{
ID: GetDeviceConfigIDStr(msg.(*device.Config).GetId()),
DevConfig: cfgData,
}, nil
}
// UpdateDatastore updates the datastore cache for all device config data.
func UpdateDatastore(ctx context.Context, client gitiles.GitilesClient, project, committish, path string) error {
var allCfgs device.AllConfigs
err := cfg2datastore.DownloadCfgProto(ctx, client, project, committish, path, &allCfgs)
if err != nil {
return errors.Fmt("UpdateDatastore - %s:%s:%s: %w", project, committish, path, err)
}
cfgs := make([]proto.Message, len(allCfgs.GetConfigs()))
for i, c := range allCfgs.GetConfigs() {
cfgs[i] = c
}
return cfg2datastore.SyncProtoToDatastore(ctx, cfgs, newDevCfgEntity)
}
// UpdateDatastoreFromBoxster updates datastore from boxster (go/boxster)
func UpdateDatastoreFromBoxster(ctx context.Context, gc git.ClientInterface, joinedConfigPath string, client gitiles.GitilesClient, project, committish, path string) error {
allDCs, err := getDeviceConfigs(ctx, gc, joinedConfigPath)
if err != nil {
return errors.Fmt("UpdateDatastoreFromBoxster: fail to read all device configs: %w", err)
}
cfgs := make([]proto.Message, len(allDCs))
for i, c := range allDCs {
cfgs[i] = c
}
logging.Debugf(ctx, "Get %d device configs from boxster", len(cfgs))
if client != nil {
var v0Cfgs device.AllConfigs
cfg2datastore.DownloadCfgProto(ctx, client, project, committish, path, &v0Cfgs)
logging.Debugf(ctx, "Get %d device configs from V0", len(v0Cfgs.GetConfigs()))
compareBoxsterWithV0(ctx, allDCs, v0Cfgs.GetConfigs())
}
return cfg2datastore.SyncProtoToDatastore(ctx, cfgs, newDevCfgEntity)
}
// GetCachedConfig gets the device config data from datastore.
func GetCachedConfig(ctx context.Context, cfgIds []*device.ConfigId) ([]proto.Message, error) {
entities := make([]cfg2datastore.EntityInterface, len(cfgIds))
for i, c := range cfgIds {
e := devcfgEntity{
ID: GetDeviceConfigIDStr(c),
}
logging.Debugf(ctx, "Getting devconfig for ID: '%s'", e.ID)
entities[i] = &e
}
return cfg2datastore.GetCachedCfgByIds(ctx, entities)
}
// GetAllCachedConfig gets all the device configs from datastore.
func GetAllCachedConfig(ctx context.Context) (map[*device.Config]time.Time, error) {
var entities []*devcfgEntity
if err := datastore.GetAll(ctx, datastore.NewQuery(entityKind), &entities); err != nil {
return nil, err
}
configs := make(map[*device.Config]time.Time, 0)
for _, dc := range entities {
if a, err := dc.GetMessagePayload(); err == nil {
configs[a.(*device.Config)] = dc.Updated
}
}
return configs, nil
}
// DeviceConfigsExists Checks if the device configs exist in the datastore
func DeviceConfigsExists(ctx context.Context, cfgIds []*device.ConfigId) (map[int32]bool, error) {
entities := make([]cfg2datastore.EntityInterface, len(cfgIds))
for i, c := range cfgIds {
e := devcfgEntity{
ID: GetDeviceConfigIDStr(c),
}
logging.Debugf(ctx, "Check devconfig for ID: '%s'", e.ID)
entities[i] = &e
}
res, err := datastore.Exists(ctx, entities)
if err != nil {
return nil, err
}
m := make(map[int32]bool, 0)
for i, r := range res.List(0) {
if r {
m[int32(i)] = true
}
}
return m, err
}