blob: 7b18ed5c66a3135fec5d885688a71826093b1f45 [file]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package inventory
import (
"bytes"
"context"
"strings"
"time"
"github.com/golang/protobuf/jsonpb"
"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/gae/service/datastore"
"infra/appengine/crosskylabadmin/app/config"
"infra/appengine/crosskylabadmin/app/gitstore"
"infra/libs/skylab/inventory"
)
// DeviceConfigID includes required info to form a device config ID.
type DeviceConfigID struct {
PlatformID string
ModelID string
VariantID string
}
// getDeviceConfigIDStr generates device id for a DUT.
func getDeviceConfigIDStr(ctx context.Context, dcID DeviceConfigID) string {
return strings.Join([]string{
strings.ToLower(dcID.PlatformID),
strings.ToLower(dcID.ModelID),
strings.ToLower(dcID.VariantID),
}, ".")
}
func getIDForDeviceConfig(ctx context.Context, dc *device.Config) string {
return getDeviceConfigIDStr(ctx, DeviceConfigID{
PlatformID: strings.ToLower(dc.Id.PlatformId.Value),
ModelID: strings.ToLower(dc.Id.ModelId.Value),
VariantID: strings.ToLower(dc.Id.VariantId.Value),
})
}
func getIDForInventoryLabels(ctx context.Context, sl *inventory.SchedulableLabels) string {
return getDeviceConfigIDStr(ctx, DeviceConfigID{
PlatformID: strings.ToLower(sl.GetBoard()),
ModelID: strings.ToLower(sl.GetModel()),
VariantID: strings.ToLower(sl.GetSku()),
})
}
// UpdateLabelsWithDeviceConfig update skylab inventory labels with cached device config.
func UpdateLabelsWithDeviceConfig(ctx context.Context, sl *inventory.SchedulableLabels) error {
dcID := getIDForInventoryLabels(ctx, sl)
if dcID == getDeviceConfigIDStr(ctx, DeviceConfigID{}) {
return errors.Reason("no platform, model, variant are specified in inventory labels").Err()
}
dce := &deviceConfigEntity{
ID: dcID,
}
if err := datastore.Get(ctx, dce); err != nil {
if datastore.IsErrNoSuchEntity(err) {
logging.Infof(ctx, "cannot find device id %s in datastore, no sync", dcID)
return nil
}
return errors.Annotate(err, "fail to get device config by id %s", dcID).Err()
}
var dc device.Config
if err := proto.Unmarshal(dce.DeviceConfig, &dc); err != nil {
return errors.Annotate(err, "fail to unmarshal device config for id %s", dce.ID).Err()
}
spec := &inventory.CommonDeviceSpecs{
Labels: &inventory.SchedulableLabels{},
}
inventory.ConvertDeviceConfig(&dc, spec)
logging.Infof(ctx, "successfully convert device config")
inventory.CopyDCAmongLabels(sl, spec.GetLabels())
logging.Infof(ctx, "successfully copy device config")
return nil
}
// GetDeviceConfig fetch device configs from git.
func GetDeviceConfig(ctx context.Context, gitilesC gitstore.GitilesClient) (map[string]*device.Config, error) {
cfg := config.Get(ctx).Inventory
gf := gitstore.FilesSpec{
Project: cfg.DeviceConfigProject,
Branch: cfg.DeviceConfigBranch,
Paths: []string{cfg.DeviceConfigPath},
}
files, err := gitstore.FetchFiles(ctx, gitilesC, gf)
if err != nil {
return nil, errors.Annotate(err, "fail to fetch device configs based on %s:%s:%v", gf.Project, gf.Branch, gf.Paths).Err()
}
data, ok := files[cfg.DeviceConfigPath]
if !ok {
return nil, errors.Reason("no device config in path %s/%s", cfg.DeviceConfigProject, cfg.DeviceConfigPath).Err()
}
unmarshaler := jsonpb.Unmarshaler{AllowUnknownFields: true}
allConfigs := device.AllConfigs{}
err = unmarshaler.Unmarshal(bytes.NewReader([]byte(data)), &allConfigs)
if err != nil {
return nil, errors.Annotate(err, "fail to unmarshal device config").Err()
}
deviceConfigs := make(map[string]*device.Config, 0)
for _, c := range allConfigs.Configs {
id := getIDForDeviceConfig(ctx, c)
if _, found := deviceConfigs[id]; found {
logging.Infof(ctx, "found duplicated id: %s id")
} else {
deviceConfigs[id] = c
}
}
return deviceConfigs, nil
}
// SaveDeviceConfig save device configs to datastore for updateDutLabel check.
func SaveDeviceConfig(ctx context.Context, deviceConfigs map[string]*device.Config) error {
updated := time.Now().UTC()
dcs := make([]*deviceConfigEntity, 0, len(deviceConfigs))
for configID, v := range deviceConfigs {
key := datastore.MakeKey(ctx, DeviceConfigKind, configID)
res, err := datastore.Exists(ctx, key)
if err != nil {
logging.Warningf(ctx, "fail to check if device config id %s exists", configID)
}
if err == nil && res.Any() {
logging.Warningf(ctx, "device config id %s already exists", configID)
}
data, err := proto.Marshal(v)
if err != nil {
logging.Warningf(ctx, "cannot marshal device config %s (id %s)", v.String(), configID)
continue
}
dcs = append(dcs, &deviceConfigEntity{
ID: configID,
DeviceConfig: data,
Updated: updated,
})
}
if err := datastore.Put(ctx, dcs); err != nil {
return errors.Annotate(err, "save device config").Err()
}
return nil
}
const (
// DeviceConfigKind is the datastore entity kind for device config entities.
DeviceConfigKind string = "DeviceConfig"
)
type deviceConfigEntity struct {
_kind string `gae:"$kind,DeviceConfig"`
ID string `gae:"$id"`
// Serialized *device.Config
DeviceConfig []byte `gae:",noindex"`
Updated time.Time
}