blob: 202830ee9fc4e586230a873ca087f868f232e078 [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 controller
import (
"context"
"fmt"
"strings"
"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"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
iv2api "infra/appengine/cros/lab_inventory/api/v1"
ufspb "infra/unifiedfleet/api/v1/models"
ufsdevice "infra/unifiedfleet/api/v1/models/chromeos/device"
chromeosLab "infra/unifiedfleet/api/v1/models/chromeos/lab"
ufsmanufacturing "infra/unifiedfleet/api/v1/models/chromeos/manufacturing"
"infra/unifiedfleet/app/config"
"infra/unifiedfleet/app/external"
ufsds "infra/unifiedfleet/app/model/datastore"
"infra/unifiedfleet/app/model/inventory"
"infra/unifiedfleet/app/model/registration"
"infra/unifiedfleet/app/model/state"
"infra/unifiedfleet/app/util"
"infra/unifiedfleet/app/util/osutil"
)
const (
// Servo port ranges from 9980 to 9999
// https://chromium.googlesource.com/chromiumos/third_party/hdctools/+/refs/heads/master/servo/servod.py#50
// However, as there are devices with servo ports < 9980. Limit the validation to 9900.
servoPortMax = 9999
servoPortMin = 9000
)
var defaultPools = []string{"DUT_POOL_QUOTA"}
// CreateDUT creates ChromeOSMachineLSE entities for a DUT.
//
// Creates one MachineLSE for DUT and updates another MachineLSE for the
// Labstation(with new Servo info from DUT)
func CreateDUT(ctx context.Context, machinelse *ufspb.MachineLSE) (*ufspb.MachineLSE, error) {
f := func(ctx context.Context) error {
hc := getHostHistoryClient(machinelse)
machinelses := []*ufspb.MachineLSE{machinelse}
// Get machine to get zone and rack info for machinelse table indexing
machine, err := GetMachine(ctx, machinelse.GetMachines()[0])
if err != nil {
return errors.Annotate(err, "Unable to get machine %s", machinelse.GetMachines()[0]).Err()
}
// Validate input
if err := validateCreateMachineLSE(ctx, machinelse, nil, machine); err != nil {
return errors.Annotate(err, "Validation error - Failed to Create ChromeOSMachineLSEDUT").Err()
}
oldMachine := proto.Clone(machine).(*ufspb.Machine)
machine.ResourceState = ufspb.State_STATE_SERVING
setOutputField(ctx, machine, machinelse)
// Check if the DUT has Servo information.
// Update Labstation MachineLSE with new Servo info unless it's ServoV3. ServoV3 devices don't have labstation info.
newServo := machinelse.GetChromeosMachineLse().GetDeviceLse().GetDut().GetPeripherals().GetServo()
if newServo != nil && newServo.GetServoHostname() != "" && !util.ServoV3HostnameRegex.MatchString(newServo.GetServoHostname()) {
// Check if the Labstation MachineLSE exists in the system.
labstationMachinelse, err := getLabstationMachineLSE(ctx, newServo.GetServoHostname())
if err != nil {
return errors.Annotate(err, "Validation error - Cannot get labstation").Err()
}
// Clone a copy for logging.
oldLabstationMachineLseCopy := proto.Clone(labstationMachinelse).(*ufspb.MachineLSE)
// Client to log labstation changes.
hcLabstation := getHostHistoryClient(labstationMachinelse)
// Check if the servo port is assigned, If missing assign a new one.
if err := assignServoPortIfMissing(labstationMachinelse, newServo); err != nil {
return err
}
// Check if the ServoHostName and ServoPort are already in use
_, err = validateServoInfoForDUT(ctx, newServo, machinelse.GetName())
if err != nil {
return err
}
// Clean servo type and servo topology as that will be updated from SSW.
cleanPreDeployFields(newServo)
// Update the Labstation MachineLSE with new Servo information.
// Append the new Servo entry to the Labstation
if err := appendServoEntryToLabstation(ctx, newServo, labstationMachinelse); err != nil {
return err
}
machinelses = append(machinelses, labstationMachinelse)
// Log labstation changes to history client.
hcLabstation.LogMachineLSEChanges(oldLabstationMachineLseCopy, labstationMachinelse)
if err := hc.SaveChangeEvents(ctx); err != nil {
return err
}
}
// BatchUpdate both DUT (and its machine), and Labstation
if _, err := registration.BatchUpdateMachines(ctx, []*ufspb.Machine{machine}); err != nil {
return errors.Annotate(err, "Fail to update machine %s", machine.GetName()).Err()
}
hc.LogMachineChanges(oldMachine, machine)
machinelse.ResourceState = ufspb.State_STATE_REGISTERED
_, err = inventory.BatchUpdateMachineLSEs(ctx, machinelses)
if err != nil {
return errors.Annotate(err, "Failed to BatchUpdate MachineLSEs").Err()
}
hc.LogMachineLSEChanges(nil, machinelse)
// Update states
if err := hc.stUdt.addLseStateHelper(ctx, machinelse, machine); err != nil {
return err
}
return hc.SaveChangeEvents(ctx)
}
if err := datastore.RunInTransaction(ctx, f, nil); err != nil {
logging.Errorf(ctx, "Failed to create MachineLSE in datastore: %s", err)
return nil, err
}
return machinelse, nil
}
// UpdateDUT updates a chrome OS DUT.
//
// updates one MachineLSE for DUT and updates Labstation MachineLSE
// (with new Servo info from DUT). If DUT is connected to the same
// Labstation but different port, The servo entry in Labstation is updated.
// If DUT is connected to a different labstation, then old servo info of DUT
// is removed from old Labstation and new servo info from the DUT is added
// to the new labstation.
func UpdateDUT(ctx context.Context, machinelse *ufspb.MachineLSE, mask *field_mask.FieldMask) (*ufspb.MachineLSE, error) {
f := func(ctx context.Context) error {
hc := getHostHistoryClient(machinelse)
// Get the existing MachineLSE(DUT).
oldMachinelse, err := inventory.GetMachineLSE(ctx, machinelse.GetName())
if err != nil {
return errors.Annotate(err, "Failed to get existing MachineLSE").Err()
}
// Validate that we are updating a DUT. Will lead to segfault later otherwise.
if oldMachinelse.GetChromeosMachineLse() == nil || oldMachinelse.GetChromeosMachineLse().GetDeviceLse().GetDut() == nil {
return errors.Reason("%s is not a DUT. Cannot update", machinelse.GetName()).Err()
}
// Validate the input. Not passing the update mask as there is a different validation for dut.
if err := validateUpdateMachineLSE(ctx, oldMachinelse, machinelse, nil); err != nil {
return errors.Annotate(err, "Validation error - Failed to update ChromeOSMachineLSEDUT").Err()
}
var machine *ufspb.Machine
// Validate the update mask and process it.
if mask != nil && len(mask.Paths) > 0 {
if err := validateUpdateMachineLSEDUTMask(mask, machinelse); err != nil {
return err
}
machinelse, err = processUpdateMachineLSEUpdateMask(ctx, proto.Clone(oldMachinelse).(*ufspb.MachineLSE), machinelse, mask)
if err != nil {
return err
}
} else {
// Full update, Machines cannot be empty.
if len(machinelse.GetMachines()) > 0 {
if len(oldMachinelse.GetMachines()) == 0 {
return errors.Reason("DUT in invalid state. Delete DUT and recreate").Err()
}
// Check if the machines have been changed.
if machinelse.GetMachines()[0] != oldMachinelse.GetMachines()[0] {
// Ignore error as validateUpdateMachineLSE verifies that the given machine exists.
machine, _ = GetMachine(ctx, machinelse.GetMachines()[0])
setOutputField(ctx, machine, machinelse)
}
} else {
// Empty machines field, Invalid update.
return status.Error(codes.InvalidArgument, "machines field cannot be empty/nil.")
}
// Copy state if its not updated.
if machinelse.GetResourceState() == ufspb.State_STATE_UNSPECIFIED {
machinelse.ResourceState = oldMachinelse.GetResourceState()
}
}
machinelses := []*ufspb.MachineLSE{machinelse}
// Extract old and new servo.
newServo := machinelse.GetChromeosMachineLse().GetDeviceLse().GetDut().GetPeripherals().GetServo()
oldServo := oldMachinelse.GetChromeosMachineLse().GetDeviceLse().GetDut().GetPeripherals().GetServo()
var servoUpdated bool
// Check if servo is being updated. We can avoid updating servo otherwise.
if mask != nil && len(mask.Paths) > 0 {
servoUpdated = util.ContainsAnyStrings(mask.Paths,
"dut.servo.hostname",
"dut.servo.port",
"dut.servo.serial",
"dut.servo.type",
"dut.servo.topology",
"dut.servo.fwchannel")
} else {
// Update servo on full update
servoUpdated = true
}
// Common refs to avoid multiple queries
var newLabstationMachinelse *ufspb.MachineLSE
var hcNewLabstation *HistoryClient
// Remove the old Servo info from labstation record. Unless it's servo V3 device
if servoUpdated && oldServo != nil && oldServo.GetServoHostname() != "" && !util.ServoV3HostnameRegex.MatchString(oldServo.GetServoHostname()) {
// Remove the servo from the labstation
oldLabstationMachinelse, err := inventory.GetMachineLSE(ctx, oldServo.GetServoHostname())
if err != nil && !util.IsNotFoundError(err) {
// Avoid returning error if we don't find the labstation
return err
}
if oldLabstationMachinelse != nil {
// Copy for logging history
oldLabstationMachineLseCopy := proto.Clone(oldLabstationMachinelse).(*ufspb.MachineLSE)
hcOldLabstation := getHostHistoryClient(oldLabstationMachinelse)
// Remove servo from labstation
if err := removeServoEntryFromLabstation(ctx, oldServo, oldLabstationMachineLseCopy); err != nil {
return err
}
// Log servo removal
hcOldLabstation.LogMachineLSEChanges(oldLabstationMachinelse, oldLabstationMachineLseCopy)
// Updating on the same labstation. Don't save the lse yet
if newServo != nil && newServo.GetServoHostname() == oldServo.GetServoHostname() {
// If we are updating on the same labstation, avoid updating yet
newLabstationMachinelse = oldLabstationMachineLseCopy
hcNewLabstation = hcOldLabstation
} else {
// Record labstation changes
if err := hcOldLabstation.SaveChangeEvents(ctx); err != nil {
return err
}
machinelses = append(machinelses, oldLabstationMachineLseCopy)
}
}
}
// Process newServo and getNewLabstationMachinelse if available. Don't care about labstation if it's a ServoV3 device
if servoUpdated && newServo != nil && newServo.GetServoHostname() != "" && !util.ServoV3HostnameRegex.MatchString(newServo.GetServoHostname()) {
if newLabstationMachinelse == nil {
// Check if the Labstation MachineLSE exists in the system first. No use doing anything if it doesn't exist.
newLabstationMachinelse, err = getLabstationMachineLSE(ctx, newServo.GetServoHostname())
if err != nil {
return err
}
// For logging new Labstation changes.
hcNewLabstation = getHostHistoryClient(newLabstationMachinelse)
}
// Check if a servo port is assigned. Assign one if its not.
if err := assignServoPortIfMissing(newLabstationMachinelse, newServo); err != nil {
return err
}
// Check if the ServoHostName and ServoPort are already in use.
_, err = validateServoInfoForDUT(ctx, newServo, machinelse.GetName())
if err != nil {
return err
}
// Make a copy to log changes for the labstation.
newLabstationMachinelseCopy := proto.Clone(newLabstationMachinelse).(*ufspb.MachineLSE)
// Append the newServo entry of DUT to the newLabstationMachinelse.
if err := appendServoEntryToLabstation(ctx, newServo, newLabstationMachinelse); err != nil {
return err
}
hcNewLabstation.LogMachineLSEChanges(newLabstationMachinelseCopy, newLabstationMachinelse)
if err := hcNewLabstation.SaveChangeEvents(ctx); err != nil {
return err
}
machinelses = append(machinelses, newLabstationMachinelse)
}
// BatchUpdate both DUT and Labstation(s)
_, err = inventory.BatchUpdateMachineLSEs(ctx, machinelses)
if err != nil {
logging.Errorf(ctx, "Failed to BatchUpdate ChromeOSMachineLSEDUTs %s", err)
return err
}
hc.LogMachineLSEChanges(oldMachinelse, machinelse)
// Update state changes.
dutState := machinelse.GetResourceState()
if err := hc.stUdt.updateStateHelper(ctx, dutState); err != nil {
return err
}
return hc.SaveChangeEvents(ctx)
}
if err := datastore.RunInTransaction(ctx, f, nil); err != nil {
logging.Errorf(ctx, "Failed to update MachineLSE DUT in datastore: %s", err)
return nil, errors.Annotate(err, "UpdateDUT - failed transaction").Err()
}
return machinelse, nil
}
// assignServoPortIfMissing assigns a servo port to the given servo
// if it's missing. Returns error if the port is out of range.
func assignServoPortIfMissing(labstation *ufspb.MachineLSE, newServo *chromeosLab.Servo) error {
// If servo port is assigned, nothing is modified.
if newServo.GetServoPort() != 0 {
// If the servo is assigned in an invalid range return error
if port := newServo.GetServoPort(); int(port) > servoPortMax || int(port) < servoPortMin {
return errors.Reason("Servo port %v is invalid. Valid servo port range [%v, %v]", port, servoPortMax, servoPortMin).Err()
}
return nil
}
// If servo is a servo v3 host then assign port 9999
// TODO(anushruth): Avoid hostname regex by querying machine.
if util.ServoV3HostnameRegex.MatchString(newServo.GetServoHostname()) {
newServo.ServoPort = int32(servoPortMax)
return nil
}
ports := make(map[int32]struct{}) // set of ports
servos := labstation.GetChromeosMachineLse().GetDeviceLse().GetLabstation().GetServos()
for _, servo := range servos {
ports[servo.GetServoPort()] = struct{}{} // Assign an empty struct. Note: Empty structs don't take memory
}
for idx := int32(servoPortMax); idx >= int32(servoPortMin); idx-- {
// Assign the highest port available to the servo
if _, ok := ports[idx]; !ok {
newServo.ServoPort = int32(idx)
return nil
}
}
return errors.Reason("Unable to assign a servo port. Check if %s has ports available", labstation.GetHostname()).Err()
}
// validateDeviceConfig checks if the corresponding device config exists in IV2
//
// Checks if the device configuration is known by querying IV2. Returns error if the device config doesn't exist.
func validateDeviceConfig(ctx context.Context, dut *ufspb.Machine) error {
devCfgIds := make([]*device.ConfigId, 0, 0)
devConfigID, err := extractDeviceConfigID(dut)
if err != nil {
return err
}
devCfgIds = append(devCfgIds, devConfigID)
if fallBackDevConfigID := getFallbackDeviceConfigID(devConfigID); fallBackDevConfigID != nil {
devCfgIds = append(devCfgIds, fallBackDevConfigID)
}
invV2Client, err := getInventoryV2Client(ctx)
if err != nil {
return err
}
resp, err := invV2Client.DeviceConfigsExists(ctx, &iv2api.DeviceConfigsExistsRequest{
ConfigIds: devCfgIds,
})
if err != nil {
return errors.Annotate(err, "Device config validation failed").Err()
}
for i := range resp.GetExists() {
if resp.GetExists()[i] {
return nil
}
}
return status.Error(codes.InvalidArgument, fmt.Sprintf("Device config doesn't exist for %s", devConfigID.String()))
}
// extractDeviceConfigID returns a corresponding ConfigID object from machine.
func extractDeviceConfigID(dut *ufspb.Machine) (*device.ConfigId, error) {
crosMachine := dut.GetChromeosMachine()
if crosMachine == nil {
return nil, errors.Reason("Invalid machine type. Not a chrome OS machine").Err()
}
// Convert the build target and model to lower case to avoid mismatch due to case.
buildTarget := strings.ToLower(crosMachine.GetBuildTarget())
model := strings.ToLower(crosMachine.GetModel())
devConfigID := &device.ConfigId{
PlatformId: &device.PlatformId{
Value: buildTarget,
},
ModelId: &device.ModelId{
Value: model,
},
}
sku := strings.ToLower(crosMachine.GetSku())
if sku != "" {
devConfigID.VariantId = &device.VariantId{
Value: sku,
}
}
return devConfigID, nil
}
func getFallbackDeviceConfigID(oldConfigID *device.ConfigId) *device.ConfigId {
if oldConfigID.GetVariantId().GetValue() != "" {
fallbackID := proto.Clone(oldConfigID).(*device.ConfigId)
fallbackID.VariantId = nil
return fallbackID
}
return nil
}
// cleanPreDeployFields clears servo type and topology.
func cleanPreDeployFields(servo *chromeosLab.Servo) {
servo.ServoType = ""
servo.ServoTopology = nil
}
// validateUpdateMachineLSEDUTMask validates the input mask for the given machineLSE.
//
// Assumes that dut and mask aren't empty. This is because this function is not called otherwise.
func validateUpdateMachineLSEDUTMask(mask *field_mask.FieldMask, machinelse *ufspb.MachineLSE) error {
var servo *chromeosLab.Servo
var rpm *chromeosLab.OSRPM
// GetDut should return an object. Otherwise UpdateDUT isn't called
dut := machinelse.GetChromeosMachineLse().GetDeviceLse().GetDut()
if peripherals := dut.GetPeripherals(); peripherals != nil {
servo = peripherals.GetServo()
rpm = peripherals.GetRpm()
}
maskSet := make(map[string]struct{}) // Set of all the masks
for _, path := range mask.Paths {
maskSet[path] = struct{}{}
}
// validate the give field mask
for _, path := range mask.Paths {
switch path {
case "name":
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - name cannot be updated, delete and create a new machinelse instead.")
case "update_time":
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - update_time cannot be updated, it is a output only field.")
case "machines":
if machinelse.GetMachines() == nil || len(machinelse.GetMachines()) == 0 || machinelse.GetMachines()[0] == "" {
return status.Error(codes.InvalidArgument, "machines field cannot be empty/nil.")
}
case "dut.hostname":
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - hostname cannot be updated, delete and create a new dut.")
case "dut.servo.hostname":
if _, ok := maskSet["dut.servo.port"]; servo.GetServoHostname() == "" && ok && servo.GetServoPort() != int32(0) {
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - Cannot update servo port. Servo host is being reset.")
}
if _, ok := maskSet["dut.servo.serial"]; servo.GetServoHostname() == "" && ok && servo.GetServoSerial() != "" {
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - Cannot update servo serial. Servo host is being reset.")
}
if _, ok := maskSet["dut.servo.setup"]; servo.GetServoHostname() == "" && ok {
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - Cannot update servo setup. Servo host is being reset.")
}
if _, ok := maskSet["dut.servo.fwchannel"]; servo.GetServoHostname() == "" && ok {
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - Cannot update servo firmware channel. Servo host is being reset.")
}
case "dut.rpm.host":
// Check for deletion of the host. Outlet cannot be updated if host is deleted.
if _, ok := maskSet["dut.rpm.outlet"]; ok && rpm.GetPowerunitName() == "" && rpm.GetPowerunitOutlet() != "" {
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - Deleting rpm host deletes everything. Cannot update outlet.")
}
case "dut.rpm.outlet":
// Check for deletion of rpm outlet. This should not be possible without deleting the host.
if _, ok := maskSet["dut.rpm.host"]; rpm.GetPowerunitOutlet() == "" && (!ok || (ok && rpm.GetPowerunitName() != "")) {
return status.Error(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - Cannot remove rpm outlet. Please delete rpm.")
}
case "deploymentTicket":
case "tags":
case "description":
case "resourceState":
case "dut.pools":
case "dut.licenses":
case "dut.servo.port":
case "dut.servo.serial":
case "dut.servo.setup":
case "dut.servo.fwchannel":
case "dut.servo.type":
case "dut.servo.topology":
case "dut.chameleon.type":
case "dut.chameleon.audioboard":
case "dut.camera.type":
case "dut.audio.box":
case "dut.audio.atrus":
case "dut.audio.cable":
case "dut.cable.type":
case "dut.wifi.antennaconn":
case "dut.wifi.wificell":
case "dut.wifi.router":
case "dut.touch.mimo":
case "dut.carrier":
case "dut.chaos":
case "dut.camerabox":
case "dut.camerabox.facing":
case "dut.camerabox.light":
case "dut.usb.smarthub":
// valid fields, nothing to validate.
default:
return status.Errorf(codes.InvalidArgument, "validateUpdateMachineLSEDUTUpdateMask - unsupported update mask path %q", path)
}
}
return nil
}
// processUpdateMachineLSEUpdateMask process the update mask and returns the machine lse with updated parameters.
func processUpdateMachineLSEUpdateMask(ctx context.Context, oldMachineLse, newMachineLse *ufspb.MachineLSE, mask *field_mask.FieldMask) (*ufspb.MachineLSE, error) {
// Extract all the peripherals to avoid doing it for every update in loop.
var oldServo, newServo *chromeosLab.Servo
var oldRPM, newRPM *chromeosLab.OSRPM
oldDut := oldMachineLse.GetChromeosMachineLse().GetDeviceLse().GetDut()
newDut := newMachineLse.GetChromeosMachineLse().GetDeviceLse().GetDut()
if oldDut != nil {
if oldPeripherals := oldDut.GetPeripherals(); oldPeripherals != nil {
// Assign empty structs to avoid panics
oldServo = oldPeripherals.GetServo()
if oldServo == nil {
oldServo = &chromeosLab.Servo{}
}
oldRPM = oldPeripherals.GetRpm()
if oldRPM == nil {
oldRPM = &chromeosLab.OSRPM{}
}
}
}
if newDut != nil {
if newPeripherals := newDut.GetPeripherals(); newPeripherals != nil {
// Assign empty structs to avoid panics
newServo = newPeripherals.GetServo()
if newServo == nil {
newServo = &chromeosLab.Servo{}
}
newRPM = newPeripherals.GetRpm()
if newRPM == nil {
newRPM = &chromeosLab.OSRPM{}
}
}
}
for _, path := range mask.Paths {
switch path {
case "machines":
// Get machine to get zone and rack info for machinelse table indexing
machine, err := GetMachine(ctx, newMachineLse.GetMachines()[0])
if err != nil {
return oldMachineLse, errors.Annotate(err, "Unable to get machine %s", newMachineLse.GetMachines()[0]).Err()
}
oldMachineLse.Machines = newMachineLse.GetMachines()
// Check permission
if err := util.CheckPermission(ctx, util.InventoriesUpdate, machine.GetRealm()); err != nil {
return oldMachineLse, err
}
setOutputField(ctx, machine, oldMachineLse)
case "mlseprototype":
oldMachineLse.MachineLsePrototype = newMachineLse.GetMachineLsePrototype()
case "resourceState":
// Avoid setting unspecified state.
if newMachineLse.GetResourceState() != ufspb.State_STATE_UNSPECIFIED {
oldMachineLse.ResourceState = newMachineLse.GetResourceState()
}
case "tags":
if tags := newMachineLse.GetTags(); tags != nil && len(tags) > 0 {
// Regular tag updates append to the existing tags.
oldMachineLse.Tags = mergeTags(oldMachineLse.GetTags(), newMachineLse.GetTags())
} else {
// Updating tags without any input clears the tags.
oldMachineLse.Tags = nil
}
case "description":
oldMachineLse.Description = newMachineLse.Description
case "deploymentTicket":
oldMachineLse.DeploymentTicket = newMachineLse.GetDeploymentTicket()
default:
if strings.HasPrefix(path, "dut") {
if strings.HasPrefix(path, "dut.servo") {
processUpdateMachineLSEServoMask(oldServo, newServo, path)
continue
}
if strings.HasPrefix(path, "dut.rpm") {
processUpdateMachineLSERPMMask(oldRPM, newRPM, path)
continue
}
processUpdateMachineLSEDUTMask(oldDut, newDut, path)
}
}
}
if oldServo.GetServoHostname() != "" {
oldDut.GetPeripherals().Servo = oldServo
} else { // Reset servo if the servo host is reset.
oldDut.GetPeripherals().Servo = nil
}
if oldRPM.GetPowerunitName() != "" {
oldDut.GetPeripherals().Rpm = oldRPM
} else { // Reset RPM if the rpm host is reset.
oldDut.GetPeripherals().Rpm = nil
}
// return existing/old machinelse with new updated values.
return oldMachineLse, nil
}
// processUpdateMachineLSEUDTMask returns updated dut with the new parameters from the mask.
func processUpdateMachineLSEDUTMask(oldDut, newDut *chromeosLab.DeviceUnderTest, path string) {
switch path {
case "dut.pools":
// Append/Clear the pools given.
if len(newDut.GetPools()) > 0 {
oldDut.Pools = util.AppendUniqueStrings(oldDut.GetPools(), newDut.GetPools()...)
} else {
// Clear all the pools assigned if nothing is given.
oldDut.Pools = nil
}
case "dut.licenses":
// Clean up all licenses if new input licenses are empty
if newDut.GetLicenses() == nil || len(newDut.GetLicenses()) == 0 {
oldDut.Licenses = nil
} else {
oldDut.Licenses = append(oldDut.GetLicenses(), newDut.GetLicenses()...)
}
case "dut.carrier":
oldDut.GetPeripherals().Carrier = newDut.GetPeripherals().GetCarrier()
case "dut.chaos":
oldDut.GetPeripherals().Chaos = newDut.GetPeripherals().GetChaos()
case "dut.usb.smarthub":
oldDut.GetPeripherals().SmartUsbhub = newDut.GetPeripherals().GetSmartUsbhub()
case "dut.camera.type":
// Copy the cameras list to oldDut.
oldDut.GetPeripherals().ConnectedCamera = newDut.GetPeripherals().GetConnectedCamera()
case "dut.cable.type":
// Copy the cable list to oldDut.
oldDut.GetPeripherals().Cable = newDut.GetPeripherals().GetCable()
case "dut.touch.mimo":
oldDut.GetPeripherals().Touch = newDut.GetPeripherals().GetTouch()
case "dut.camerabox":
oldDut.GetPeripherals().Camerabox = newDut.GetPeripherals().GetCamerabox()
case "dut.chameleon.type":
if oldDut.GetPeripherals().GetChameleon() == nil {
oldDut.GetPeripherals().Chameleon = &chromeosLab.Chameleon{}
}
if newDut.GetPeripherals().GetChameleon() != nil && len(newDut.GetPeripherals().GetChameleon().GetChameleonPeripherals()) > 0 {
for _, ctype := range newDut.GetPeripherals().GetChameleon().GetChameleonPeripherals() {
// Copy chameleon peripherals. Avoid copying invalid values.
if ctype != chromeosLab.ChameleonType_CHAMELEON_TYPE_INVALID {
oldDut.GetPeripherals().GetChameleon().ChameleonPeripherals = append(oldDut.GetPeripherals().GetChameleon().ChameleonPeripherals, ctype)
}
}
} else {
// Deleting chameleon
oldDut.GetPeripherals().Chameleon = nil
}
case "dut.chameleon.audioboard":
if oldDut.GetPeripherals().GetChameleon() == nil {
oldDut.GetPeripherals().Chameleon = &chromeosLab.Chameleon{}
}
if newDut.GetPeripherals().GetChameleon() != nil {
oldDut.GetPeripherals().GetChameleon().AudioBoard = newDut.GetPeripherals().GetChameleon().GetAudioBoard()
} else {
// Deleting chameleon
oldDut.GetPeripherals().Chameleon = nil
}
case "dut.wifi.antennaconn":
if oldDut.GetPeripherals().GetWifi() == nil {
oldDut.GetPeripherals().Wifi = &chromeosLab.Wifi{}
}
// If newDut doesn't have wifi. Ignore the assignment
if newDut.GetPeripherals().GetWifi() != nil {
oldDut.GetPeripherals().GetWifi().AntennaConn = newDut.GetPeripherals().GetWifi().GetAntennaConn()
} else {
// Deleting wifi
oldDut.GetPeripherals().Wifi = nil
}
case "dut.wifi.wificell":
if oldDut.GetPeripherals().GetWifi() == nil {
oldDut.GetPeripherals().Wifi = &chromeosLab.Wifi{}
}
// If newDut doesn't have wifi. Ignore the assignment
if newDut.GetPeripherals().GetWifi() != nil {
oldDut.GetPeripherals().GetWifi().Wificell = newDut.GetPeripherals().GetWifi().GetWificell()
} else {
// Deleting wifi
oldDut.GetPeripherals().Wifi = nil
}
case "dut.wifi.router":
if oldDut.GetPeripherals().GetWifi() == nil {
oldDut.GetPeripherals().Wifi = &chromeosLab.Wifi{}
}
// If newDut doesn't have wifi. Ignore the assignment
if newDut.GetPeripherals().GetWifi() != nil {
oldDut.GetPeripherals().GetWifi().Router = newDut.GetPeripherals().GetWifi().GetRouter()
} else {
// Deleting wifi
oldDut.GetPeripherals().Wifi = nil
}
case "dut.audio.box":
if oldDut.GetPeripherals().GetAudio() == nil {
oldDut.GetPeripherals().Audio = &chromeosLab.Audio{}
}
if newDut.GetPeripherals().GetAudio() != nil {
oldDut.GetPeripherals().GetAudio().AudioBox = newDut.GetPeripherals().GetAudio().GetAudioBox()
} else {
// Delete audio
oldDut.GetPeripherals().Audio = nil
}
case "dut.audio.atrus":
if oldDut.GetPeripherals().GetAudio() == nil {
oldDut.GetPeripherals().Audio = &chromeosLab.Audio{}
}
if newDut.GetPeripherals().GetAudio() != nil {
oldDut.GetPeripherals().GetAudio().Atrus = newDut.GetPeripherals().GetAudio().GetAtrus()
} else {
// Delete audio
oldDut.GetPeripherals().Audio = nil
}
case "dut.audio.cable":
if oldDut.GetPeripherals().GetAudio() == nil {
oldDut.GetPeripherals().Audio = &chromeosLab.Audio{}
}
if newDut.GetPeripherals().GetAudio() != nil {
oldDut.GetPeripherals().GetAudio().AudioCable = newDut.GetPeripherals().GetAudio().GetAudioCable()
} else {
// Delete audio
oldDut.GetPeripherals().Audio = nil
}
case "dut.camerabox.facing":
if oldDut.GetPeripherals().GetCameraboxInfo() == nil {
oldDut.GetPeripherals().CameraboxInfo = &chromeosLab.Camerabox{}
}
if newDut.GetPeripherals().GetCameraboxInfo() != nil {
oldDut.GetPeripherals().CameraboxInfo.Facing = newDut.GetPeripherals().GetCameraboxInfo().GetFacing()
} else {
oldDut.GetPeripherals().CameraboxInfo = nil
}
case "dut.camerabox.light":
if oldDut.GetPeripherals().GetCameraboxInfo() == nil {
oldDut.GetPeripherals().CameraboxInfo = &chromeosLab.Camerabox{}
}
if newDut.GetPeripherals().GetCameraboxInfo() != nil {
oldDut.GetPeripherals().CameraboxInfo.Light = newDut.GetPeripherals().GetCameraboxInfo().GetLight()
} else {
oldDut.GetPeripherals().CameraboxInfo = nil
}
}
return
}
// processUpdateMachineLSEServoMask returns servo with new updated params from the mask.
func processUpdateMachineLSEServoMask(oldServo, newServo *chromeosLab.Servo, path string) {
switch path {
case "dut.servo.hostname":
oldServo.ServoHostname = newServo.GetServoHostname()
case "dut.servo.port":
oldServo.ServoPort = newServo.GetServoPort()
case "dut.servo.serial":
oldServo.ServoSerial = newServo.GetServoSerial()
case "dut.servo.type":
oldServo.ServoType = newServo.GetServoType()
case "dut.servo.topology":
oldServo.ServoTopology = newServo.GetServoTopology()
case "dut.servo.fwchannel":
oldServo.ServoFwChannel = newServo.GetServoFwChannel()
}
}
// processUpdateMacineLSERPMMask returns rpm with new updated params from the mask
func processUpdateMachineLSERPMMask(oldRPM, newRPM *chromeosLab.OSRPM, path string) {
switch path {
case "dut.rpm.host":
oldRPM.PowerunitName = newRPM.GetPowerunitName()
case "dut.rpm.outlet":
oldRPM.PowerunitOutlet = newRPM.GetPowerunitOutlet()
}
}
// GetChromeOSDeviceData returns ChromeOSDeviceData for the given id/hostname from InvV2 and UFS.
func GetChromeOSDeviceData(ctx context.Context, id, hostname string) (*ufspb.ChromeOSDeviceData, error) {
var lse *ufspb.MachineLSE
var err error
if hostname != "" {
lse, err = GetMachineLSE(ctx, hostname)
if err != nil {
return nil, err
}
if len(lse.GetMachines()) != 0 {
id = lse.GetMachines()[0]
}
} else {
machinelses, err := inventory.QueryMachineLSEByPropertyName(ctx, "machine_ids", id, false)
if err != nil {
return nil, err
}
if len(machinelses) == 0 {
return nil, status.Error(codes.NotFound, fmt.Sprintf("DUT not found for asset id %s", id))
}
lse = machinelses[0]
}
dutState, err := state.GetDutState(ctx, id)
if err != nil {
logging.Warningf(ctx, "DutState for %s not found. Error: %s", id, err)
}
machine, err := GetMachine(ctx, id)
if err != nil {
logging.Errorf(ctx, "Machine for %s not found. Error: %s", id, err)
return &ufspb.ChromeOSDeviceData{
LabConfig: lse,
}, nil
}
invV2Client, err := getInventoryV2Client(ctx)
if err != nil {
logging.Errorf(ctx, "Failed to InvV2Client. Error: %s", err)
return &ufspb.ChromeOSDeviceData{
LabConfig: lse,
}, nil
}
devConfig, err := getDeviceConfig(ctx, invV2Client, machine)
if err != nil {
logging.Warningf(ctx, "DeviceConfig for %s not found. Error: %s", id, err)
}
hwid := machine.GetChromeosMachine().GetHwid()
mfgConfig, err := getManufacturingConfig(ctx, invV2Client, hwid)
if err != nil {
logging.Warningf(ctx, "ManufacturingConfig for %s not found. Error: %s", hwid, err)
}
hwidData, err := getHwidData(ctx, invV2Client, hwid)
if err != nil {
logging.Warningf(ctx, "Hwid data for %s not found. Error: %s", hwid, err)
}
data := &ufspb.ChromeOSDeviceData{
LabConfig: lse,
Machine: machine,
DeviceConfig: devConfig,
ManufacturingConfig: mfgConfig,
HwidData: hwidData,
DutState: dutState,
}
dutV1, err := osutil.AdaptToV1DutSpec(data)
if err != nil {
return nil, status.Errorf(codes.Internal, "Cannot AdaptToV1DutSpec %s", err)
}
data.DutV1 = dutV1
return data, nil
}
// getDeviceConfig get device config form InvV2
func getDeviceConfig(ctx context.Context, inv2Client external.CrosInventoryClient, machine *ufspb.Machine) (*ufsdevice.Config, error) {
devConfigID, err := extractDeviceConfigID(machine)
if err != nil {
return nil, err
}
resp, err := inv2Client.GetDeviceConfig(ctx, &iv2api.GetDeviceConfigRequest{
ConfigId: devConfigID,
})
if err != nil {
return nil, err
}
s := proto.MarshalTextString(resp)
var devConfig ufsdevice.Config
proto.UnmarshalText(s, &devConfig)
logging.Debugf(ctx, "InvV2 device config:\n %+v\nUFS device config:\n %+v ", resp, &devConfig)
return &devConfig, err
}
// getManufacturingConfig get manufacturing config form InvV2
func getManufacturingConfig(ctx context.Context, inv2Client external.CrosInventoryClient, id string) (*ufsmanufacturing.ManufacturingConfig, error) {
resp, err := inv2Client.GetManufacturingConfig(ctx, &iv2api.GetManufacturingConfigRequest{
Name: id,
})
if err != nil {
return nil, err
}
s := proto.MarshalTextString(resp)
var mfgConfig ufsmanufacturing.ManufacturingConfig
proto.UnmarshalText(s, &mfgConfig)
logging.Debugf(ctx, "InvV2 manufacturing config:\n %+v\nUFS manufacturing config:\n %+v ", resp, &mfgConfig)
return &mfgConfig, err
}
// getHwidData get hwid data form InvV2
func getHwidData(ctx context.Context, inv2Client external.CrosInventoryClient, id string) (*ufspb.HwidData, error) {
resp, err := inv2Client.GetHwidData(ctx, &iv2api.GetHwidDataRequest{
Name: id,
})
if err != nil {
return nil, err
}
s := proto.MarshalTextString(resp)
var hwidData ufspb.HwidData
proto.UnmarshalText(s, &hwidData)
logging.Debugf(ctx, "InvV2 hwid data:\n %+v\nUFS hwid data:\n %+v ", resp, &hwidData)
return &hwidData, err
}
func getInventoryV2Client(ctx context.Context) (external.CrosInventoryClient, error) {
es, err := external.GetServerInterface(ctx)
if err != nil {
return nil, err
}
return es.NewCrosInventoryInterfaceFactory(ctx, config.Get(ctx).GetCrosInventoryHost())
}
func getRPMNamePortForOSMachineLSE(lse *ufspb.MachineLSE) (string, string) {
if lse.GetChromeosMachineLse() != nil {
if lse.GetChromeosMachineLse().GetDeviceLse().GetDut() != nil {
return lse.GetChromeosMachineLse().GetDeviceLse().GetDut().GetPeripherals().GetRpm().GetPowerunitName(),
lse.GetChromeosMachineLse().GetDeviceLse().GetDut().GetPeripherals().GetRpm().GetPowerunitOutlet()
} else if lse.GetChromeosMachineLse().GetDeviceLse().GetLabstation() != nil {
return lse.GetChromeosMachineLse().GetDeviceLse().GetLabstation().GetRpm().GetPowerunitName(),
lse.GetChromeosMachineLse().GetDeviceLse().GetLabstation().GetRpm().GetPowerunitOutlet()
}
}
return "", ""
}
// renameDUT deletes the dut with oldName and creates one with newName. Use inside a transaction.
func renameDUT(ctx context.Context, oldName, newName string, lse *ufspb.MachineLSE, machine *ufspb.Machine) (*ufspb.MachineLSE, error) {
// Check if we can rename the host
if err := validateRenameDUT(ctx, oldName, newName, lse); err != nil {
return nil, err
}
hc := getHostHistoryClient(lse)
newLse := proto.Clone(lse).(*ufspb.MachineLSE)
// Delete the old host record
if err := inventory.DeleteMachineLSE(ctx, oldName); err != nil {
return nil, errors.Annotate(err, "Failed to remove machineLSE").Err()
}
// Delete old state record for host. Avoid deleting machine state.
if err := hc.stUdt.deleteLseStateHelper(ctx, lse, nil); err != nil {
return nil, errors.Annotate(err, "Fail to delete lse-related states").Err()
}
if err := hc.SaveChangeEvents(ctx); err != nil {
return nil, errors.Annotate(err, "Failed to log changes").Err()
}
// Update the host name
newLse.Name = newName
newLse.Hostname = newName
newLse.GetChromeosMachineLse().GetDeviceLse().GetDut().Hostname = newName
if _, err := inventory.BatchUpdateMachineLSEs(ctx, []*ufspb.MachineLSE{newLse}); err != nil {
return nil, errors.Annotate(err, "Failed to add MachineLSE").Err()
}
// Update states
if err := hc.stUdt.addLseStateHelper(ctx, newLse, machine); err != nil {
return nil, err
}
hc.LogMachineLSEChanges(lse, newLse)
if err := hc.SaveChangeEvents(ctx); err != nil {
return nil, errors.Annotate(err, "Failed to save history").Err()
}
return newLse, nil
}
func validateRenameDUT(ctx context.Context, oldName, newName string, lse *ufspb.MachineLSE) error {
// Check if it's part of scheduling unit. It might be possible to rename these in future.
schedulingUnits, err := inventory.QuerySchedulingUnitByPropertyNames(ctx, map[string]string{"machinelses": oldName}, true)
if err != nil {
return errors.Annotate(err, "failed to query SchedulingUnit for machinelses %s", oldName).Err()
}
if len(schedulingUnits) > 0 {
return status.Errorf(codes.FailedPrecondition, fmt.Sprintf("DUT %s is associated with SchedulingUnit %s. It's not possible to rename this at the moment", oldName, schedulingUnits[0].GetName()))
}
return nil
}
// GetDUTConnectedToServo returns machineLSE of DUT connected to the servo
func GetDUTConnectedToServo(ctx context.Context, servo *chromeosLab.Servo) (*ufspb.MachineLSE, error) {
servoID := ufsds.GetServoID(servo.GetServoHostname(), servo.GetServoPort())
dut, err := inventory.QueryMachineLSEByPropertyName(ctx, "servo_id", servoID, false)
if err != nil {
return nil, err
}
// Return dut if we have one
if len(dut) == 1 {
return dut[0], nil
}
if len(dut) > 1 {
return nil, status.Errorf(codes.Internal, "Misconfigured DUTs. Muiltple DUTS(%d) found with same servo config", len(dut))
}
return nil, nil
}