blob: 5e3890d8312928f67b4a54020df0dd40fa044d7b [file] [log] [blame]
// Copyright 2020 The Chromium 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 dutstate provides representation of states of DUT in Swarming
// and reading and updating a state in UFS service.
package dutstate
import (
"context"
"log"
"go.chromium.org/luci/common/errors"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
ufsProto "infra/unifiedfleet/api/v1/models"
ufsAPI "infra/unifiedfleet/api/v1/rpc"
ufsUtil "infra/unifiedfleet/app/util"
)
// State is an enum for host state.
type State string
// All DUT states.
const (
// Device ready to run tests.
Ready State = "ready"
// Provisioning failed on the device and required verified and repair.
NeedsRepair State = "needs_repair"
// Test failed on the device and required reset the state.
NeedsReset State = "needs_reset"
// Device did not recovered after running repair task on it.
RepairFailed State = "repair_failed"
// Device prepared to be deployed to the lab.
NeedsDeploy State = "needs_deploy"
// Device reserved for analysis or hold by lab
Reserved State = "reserved"
// Device under manual repair interaction by lab
ManualRepair State = "manual_repair"
// Device required manual attention to be fixed
NeedsManualRepair State = "needs_manual_repair"
// Device is not fixable due issues with hardware and has to be replaced
NeedsReplacement State = "needs_replacement"
// Device state when state is not present or cannot be read from UFS.
Unknown State = "unknown"
)
// Info represent information of the state and last updated time.
type Info struct {
// State represents the state of the DUT from Swarming.
State State
// Time represents in Unix time of the last updated DUT state recorded.
Time int64
}
// UFSClient represents short set of method of ufsAPI.FleetClient.
type UFSClient interface {
GetMachineLSE(ctx context.Context, req *ufsAPI.GetMachineLSERequest, opts ...grpc.CallOption) (*ufsProto.MachineLSE, error)
UpdateMachineLSE(ctx context.Context, req *ufsAPI.UpdateMachineLSERequest, opts ...grpc.CallOption) (*ufsProto.MachineLSE, error)
}
// String provides string representation of the DUT state.
func (s State) String() string {
return string(s)
}
// Read read state from UFS.
//
// If state not exist in the UFS the state will be default and time is 0.
func Read(ctx context.Context, c UFSClient, host string) Info {
ctx = setupContext(ctx, ufsUtil.OSNamespace)
log.Printf("dutstate: Try to read DUT/Labstation state for %s", host)
res, err := c.GetMachineLSE(ctx, &ufsAPI.GetMachineLSERequest{
Name: ufsUtil.AddPrefix(ufsUtil.MachineLSECollection, host),
})
if err != nil {
if status.Code(err) == codes.NotFound {
log.Printf("dutstate: DUT/Labstation not found for %s; %s", host, err)
} else {
log.Printf("dutstate: Fail to get DUT/Labstation for %s; %s", host, err)
}
// For default state time will not set and equal 0.
return Info{
State: Unknown,
}
}
return Info{
State: convertFromUFSState(res.GetResourceState()),
Time: res.GetUpdateTime().Seconds,
}
}
// Update push new DUT/Labstation state to UFS.
func Update(ctx context.Context, c UFSClient, host string, state State) error {
ctx = setupContext(ctx, ufsUtil.OSNamespace)
ufsState := convertToUFSState(state)
// Get the MachineLSE to determine if its a DUT or a Labstation.
log.Printf("dutstate: Try to get MachineLSE for %s", host)
res, err := c.GetMachineLSE(ctx, &ufsAPI.GetMachineLSERequest{
Name: ufsUtil.AddPrefix(ufsUtil.MachineLSECollection, host),
})
if err != nil {
return errors.Annotate(err, "Failed to get DUT/Labstation for %s", host).Err()
}
log.Printf("dutstate: Try to update DUT/Labstation state %s: %q (%q)", host, state, ufsState)
res.ResourceState = ufsState
_, err = c.UpdateMachineLSE(ctx, &ufsAPI.UpdateMachineLSERequest{
MachineLSE: res,
UpdateMask: &field_mask.FieldMask{
Paths: []string{"resourceState"},
},
})
if err != nil {
return errors.Annotate(err, "set state %q for %q in UFS", state, host).Err()
}
return nil
}
func convertToUFSState(state State) ufsProto.State {
if ufsState, ok := stateToUFS[state]; ok {
return ufsState
}
return ufsProto.State_STATE_UNSPECIFIED
}
func convertFromUFSState(state ufsProto.State) State {
if s, ok := stateFromUFS[state]; ok {
return s
}
return Unknown
}
// setupContext sets up context with namespace
func setupContext(ctx context.Context, namespace string) context.Context {
md := metadata.Pairs(ufsUtil.Namespace, namespace)
return metadata.NewOutgoingContext(ctx, md)
}
var stateToUFS = map[State]ufsProto.State{
Ready: ufsProto.State_STATE_SERVING,
NeedsReset: ufsProto.State_STATE_NEEDS_RESET,
NeedsRepair: ufsProto.State_STATE_NEEDS_REPAIR,
RepairFailed: ufsProto.State_STATE_REPAIR_FAILED,
NeedsDeploy: ufsProto.State_STATE_DEPLOYED_PRE_SERVING,
Reserved: ufsProto.State_STATE_RESERVED,
ManualRepair: ufsProto.State_STATE_DEPLOYED_TESTING,
NeedsManualRepair: ufsProto.State_STATE_DISABLED,
NeedsReplacement: ufsProto.State_STATE_DECOMMISSIONED,
}
var stateFromUFS = map[ufsProto.State]State{
ufsProto.State_STATE_SERVING: Ready,
ufsProto.State_STATE_NEEDS_RESET: NeedsReset,
ufsProto.State_STATE_NEEDS_REPAIR: NeedsRepair,
ufsProto.State_STATE_REPAIR_FAILED: RepairFailed,
ufsProto.State_STATE_DEPLOYED_PRE_SERVING: NeedsDeploy,
ufsProto.State_STATE_RESERVED: Reserved,
ufsProto.State_STATE_DEPLOYED_TESTING: ManualRepair,
ufsProto.State_STATE_DISABLED: NeedsManualRepair,
ufsProto.State_STATE_DECOMMISSIONED: NeedsReplacement,
}