blob: be4b5c09cd530702d68f5ce868dc5e88c8d7ef4f [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package state
import (
"context"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
"go.chromium.org/luci/gae/service/datastore"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
chromeosLab "infra/unifiedfleet/api/v1/models/chromeos/lab"
ufsds "infra/unifiedfleet/app/model/datastore"
)
// DutStateKind is the datastore entity kind of dut state.
//
// Dut state is only specific to OS devices for now.
const DutStateKind string = "DutState"
// DutStateEntity is a datastore entity that tracks dut state.
type DutStateEntity struct {
_kind string `gae:"$kind,DutState"`
// refer to the device id
ID string `gae:"$id"`
Hostname string `gae:"hostname"`
// lab.DutState cannot be directly used as it contains pointer (timestamp).
DutState []byte `gae:",noindex"`
}
// GetProto returns the unmarshaled dut state.
func (e *DutStateEntity) GetProto() (proto.Message, error) {
var p chromeosLab.DutState
if err := proto.Unmarshal(e.DutState, &p); err != nil {
return nil, err
}
return &p, nil
}
func newDutStateEntity(ctx context.Context, pm proto.Message) (ufsds.FleetEntity, error) {
p := pm.(*chromeosLab.DutState)
if p.GetId().GetValue() == "" {
return nil, errors.Reason("Empty ID in Dut state").Err()
}
s, err := proto.Marshal(p)
if err != nil {
return nil, errors.Annotate(err, "fail to marshal DutState %s", p).Err()
}
return &DutStateEntity{
ID: p.GetId().GetValue(),
Hostname: p.GetHostname(),
DutState: s,
}, nil
}
// GetDutState returns dut state for the given id from datastore.
func GetDutState(ctx context.Context, id string) (*chromeosLab.DutState, error) {
pm, err := ufsds.Get(ctx, &chromeosLab.DutState{Id: &chromeosLab.ChromeOSDeviceID{Value: id}}, newDutStateEntity)
if err == nil {
return pm.(*chromeosLab.DutState), err
}
return nil, err
}
// UpdateDutStates updates dut states in datastore.
func UpdateDutStates(ctx context.Context, dutStates []*chromeosLab.DutState) ([]*chromeosLab.DutState, error) {
protos := make([]proto.Message, len(dutStates))
utime := ptypes.TimestampNow()
for i, ds := range dutStates {
ds.UpdateTime = utime
protos[i] = ds
}
_, err := ufsds.PutAll(ctx, protos, newDutStateEntity, true)
if err == nil {
return dutStates, err
}
return nil, err
}
func queryAllDutStates(ctx context.Context) ([]ufsds.FleetEntity, error) {
var entities []*DutStateEntity
q := datastore.NewQuery(DutStateKind)
if err := datastore.GetAll(ctx, q, &entities); err != nil {
return nil, err
}
fe := make([]ufsds.FleetEntity, len(entities))
for i, e := range entities {
fe[i] = e
}
return fe, nil
}
// ListAllDutStates returns all DutState in datastore.
func ListAllDutStates(ctx context.Context, keysOnly bool) (res []*chromeosLab.DutState, err error) {
var entities []*DutStateEntity
q := datastore.NewQuery(DutStateKind).KeysOnly(keysOnly).FirestoreMode(true)
if err = datastore.GetAll(ctx, q, &entities); err != nil {
return nil, err
}
for _, ent := range entities {
if keysOnly {
res = append(res, &chromeosLab.DutState{
Id: &chromeosLab.ChromeOSDeviceID{Value: ent.ID},
})
} else {
pm, err := ent.GetProto()
if err != nil {
logging.Errorf(ctx, "Failed to UnMarshal: %s", err)
return nil, err
}
dutState := pm.(*chromeosLab.DutState)
res = append(res, dutState)
}
}
return
}
// QueryDutStateByPropertyNames queries DutState Entity in the datastore.
// If keysOnly is true, then only key field is populated in returned DutStates.
func QueryDutStateByPropertyNames(ctx context.Context, propertyMap map[string]string, keysOnly bool) ([]*chromeosLab.DutState, error) {
q := datastore.NewQuery(DutStateKind).KeysOnly(keysOnly).FirestoreMode(true)
var entities []*DutStateEntity
for propertyName, id := range propertyMap {
q = q.Eq(propertyName, id)
}
if err := datastore.GetAll(ctx, q, &entities); err != nil {
logging.Errorf(ctx, "Failed to query from datastore: %s", err)
return nil, status.Errorf(codes.Internal, ufsds.InternalError)
}
if len(entities) == 0 {
logging.Infof(ctx, "No DutStates found for the query: %s", q.String())
return nil, nil
}
dutStates := make([]*chromeosLab.DutState, 0, len(entities))
for _, entity := range entities {
if keysOnly {
dutState := &chromeosLab.DutState{
Id: &chromeosLab.ChromeOSDeviceID{Value: entity.ID},
}
dutStates = append(dutStates, dutState)
} else {
pm, perr := entity.GetProto()
if perr != nil {
logging.Errorf(ctx, "Failed to unmarshal proto: %s", perr)
continue
}
dutStates = append(dutStates, pm.(*chromeosLab.DutState))
}
}
return dutStates, nil
}
// ListDutStates lists the DutStates.
//
// Does a query over DutState entities. Returns up to pageSize entities, plus non-nil cursor (if
// there are more results). pageSize must be positive.
func ListDutStates(ctx context.Context, pageSize int32, pageToken string, filterMap map[string][]interface{}, keysOnly bool) (res []*chromeosLab.DutState, nextPageToken string, err error) {
q, err := ufsds.ListQuery(ctx, DutStateKind, pageSize, pageToken, filterMap, keysOnly)
if err != nil {
return nil, "", err
}
var nextCur datastore.Cursor
err = datastore.Run(ctx, q, func(ent *DutStateEntity, cb datastore.CursorCB) error {
if keysOnly {
DutState := &chromeosLab.DutState{
Id: &chromeosLab.ChromeOSDeviceID{Value: ent.ID},
}
res = append(res, DutState)
} else {
pm, err := ent.GetProto()
if err != nil {
logging.Errorf(ctx, "Failed to UnMarshal: %s", err)
return nil
}
res = append(res, pm.(*chromeosLab.DutState))
}
if len(res) >= int(pageSize) {
if nextCur, err = cb(); err != nil {
return err
}
return datastore.Stop
}
return nil
})
if err != nil {
logging.Errorf(ctx, "Failed to list DutStates %s", err)
return nil, "", status.Errorf(codes.Internal, ufsds.InternalError)
}
if nextCur != nil {
nextPageToken = nextCur.String()
}
return
}
// GetAllDutStates returns all dut states in datastore.
func GetAllDutStates(ctx context.Context) (*ufsds.OpResults, error) {
return ufsds.GetAll(ctx, queryAllDutStates)
}
// DeleteDutStates deletes a batch of dut states
func DeleteDutStates(ctx context.Context, resourceNames []string) *ufsds.OpResults {
protos := make([]proto.Message, len(resourceNames))
for i, m := range resourceNames {
protos[i] = &chromeosLab.DutState{
Id: &chromeosLab.ChromeOSDeviceID{
Value: m,
},
}
}
return ufsds.DeleteAll(ctx, protos, newDutStateEntity)
}
// ImportDutStates creates or updates a batch of dut states in datastore
func ImportDutStates(ctx context.Context, dutStates []*chromeosLab.DutState) (*ufsds.OpResults, error) {
protos := make([]proto.Message, len(dutStates))
utime := ptypes.TimestampNow()
for i, m := range dutStates {
if m.UpdateTime == nil {
m.UpdateTime = utime
}
protos[i] = m
}
return ufsds.Insert(ctx, protos, newDutStateEntity, true, true)
}