blob: dfdcb17ff6918d4161ff5450455bde76eaf6fb80 [file]
// 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 inventory
import (
"context"
"math/rand"
"time"
"go.chromium.org/chromiumos/infra/proto/go/device"
"go.chromium.org/luci/common/data/stringset"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
api "infra/appengine/cros/lab_inventory/api/v1"
fleet "infra/appengine/crosskylabadmin/api/fleet/v1"
"infra/appengine/crosskylabadmin/app/gitstore"
"infra/libs/skylab/inventory"
)
type duoClient struct {
gc *gitStoreClient
ic *invServiceClient
// A number in [0, 100] indicate the write traffic (deploy/update)
// duplicated to inventory v2 service.
writeTrafficRatio int
// A number in [0, 100] indicate the read traffic fanning out to inventory
// v2 service.
readTrafficRatio int
// The uuids of migration test devices.
testingDeviceUUIDs stringset.Set
// The uuids of migration test devices.
testingDeviceNames stringset.Set
// If we still write to v1.
inventoryV2Only bool
}
func newDuoClient(ctx context.Context, gs *gitstore.InventoryStore, host string, readTrafficRatio, writeTrafficRatio int, testingUUIDs, testingNames []string, inventoryV2Only bool) (inventoryClient, error) {
gc, err := newGitStoreClient(ctx, gs)
if err != nil {
return nil, errors.Annotate(err, "create git client").Err()
}
ic, err := newInvServiceClient(ctx, host)
if err != nil {
logging.Infof(ctx, "Failed to create inventory client of the duo client. Just return the git store client")
return gc, nil
}
return &duoClient{
gc: gc.(*gitStoreClient),
ic: ic.(*invServiceClient),
readTrafficRatio: readTrafficRatio,
writeTrafficRatio: writeTrafficRatio,
testingDeviceUUIDs: stringset.NewFromSlice(testingUUIDs...),
testingDeviceNames: stringset.NewFromSlice(testingNames...),
inventoryV2Only: inventoryV2Only,
}, nil
}
func (client *duoClient) willWriteToV2() bool {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return r.Intn(100) < client.writeTrafficRatio
}
func (client *duoClient) willReadFromV2(req *fleet.GetDutInfoRequest) bool {
if req.MustFromV1 {
return false
}
if client.testingDeviceUUIDs.Has(req.GetId()) {
return true
}
if client.testingDeviceNames.Has(req.GetHostname()) {
return true
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return r.Intn(100) < client.readTrafficRatio
}
func (client *duoClient) addManyDUTsToFleet(ctx context.Context, nds []*inventory.CommonDeviceSpecs, pickServoPort bool) (url string, ds []*inventory.CommonDeviceSpecs, err error) {
if !client.inventoryV2Only {
url, ds, err = client.gc.addManyDUTsToFleet(ctx, nds, pickServoPort)
logging.Infof(ctx, "[v1] add dut result: %s, %s", url, err)
logging.Infof(ctx, "[v1] spec returned: %s", ds)
}
if client.willWriteToV2() {
url, ds, err = client.ic.addManyDUTsToFleet(ctx, nds, pickServoPort)
logging.Infof(ctx, "[v2] add dut result: %s, %s", url, err)
logging.Infof(ctx, "[v2] spec returned: %s", ds)
}
return
}
func (client *duoClient) getAssetsFromRegistration(ctx context.Context, assetIDList *api.AssetIDList) (*api.AssetResponse, error) {
ds, err := client.ic.getAssetsFromRegistration(ctx, assetIDList)
logging.Infof(ctx, "[v2] getAssetsFromRegistration assetResponse returned: %s, %s", ds, err)
return ds, err
}
func (client *duoClient) updateAssetsInRegistration(ctx context.Context, assetList *api.AssetList) (*api.AssetResponse, error) {
ds, err := client.ic.updateAssetsInRegistration(ctx, assetList)
logging.Infof(ctx, "[v2] updateAssetsInRegistration assetResponse returned: %s, %s", ds, err)
return ds, err
}
func (client *duoClient) updateDUTSpecs(ctx context.Context, od, nd *inventory.CommonDeviceSpecs, pickServoPort bool) (url string, err error) {
if !client.inventoryV2Only {
url, err = client.gc.updateDUTSpecs(ctx, od, nd, pickServoPort)
logging.Infof(ctx, "[v1] update dut result: %s, %s", url, err)
}
if client.willWriteToV2() {
url, err = client.ic.updateDUTSpecs(ctx, od, nd, pickServoPort)
logging.Infof(ctx, "[v2] update dut result: %s, %s", url, err)
}
return
}
func (client *duoClient) deleteDUTsFromFleet(ctx context.Context, ids []string) (url string, deletedIds []string, err error) {
if !client.inventoryV2Only {
url, deletedIds, err = client.gc.deleteDUTsFromFleet(ctx, ids)
logging.Infof(ctx, "[v1] delete dut result: %s, %s, %s", url, deletedIds, err)
}
if client.willWriteToV2() {
url, deletedIds, err = client.ic.deleteDUTsFromFleet(ctx, ids)
logging.Infof(ctx, "[v2] delete dut result: %s, %s, %s", url, deletedIds, err)
}
return
}
func (client *duoClient) selectDutsFromInventory(ctx context.Context, sel *fleet.DutSelector) (duts []*inventory.DeviceUnderTest, err error) {
if !client.inventoryV2Only {
duts, err = client.gc.selectDutsFromInventory(ctx, sel)
}
if client.willWriteToV2() {
duts, err = client.ic.selectDutsFromInventory(ctx, sel)
logging.Infof(ctx, "[v2] select duts by %v", sel)
if len(duts) > 0 {
logging.Infof(ctx, "[v2] selecting returns '%s'...(total %d duts)", duts[0].GetCommon().GetHostname(), len(duts))
} else {
logging.Infof(ctx, "[v2] selecting returns 0 duts")
}
}
return
}
func (client *duoClient) commitBalancePoolChanges(ctx context.Context, changes []*fleet.PoolChange) (u string, err error) {
if !client.inventoryV2Only {
u, err = client.gc.commitBalancePoolChanges(ctx, changes)
}
if client.willWriteToV2() {
u, err = client.ic.commitBalancePoolChanges(ctx, changes)
logging.Infof(ctx, "[v2] Commit balancing pool result: %s: %s", u, err)
}
return
}
func (client *duoClient) getDutInfo(ctx context.Context, req *fleet.GetDutInfoRequest) ([]byte, time.Time, error) {
if client.willReadFromV2(req) {
dut, now, err := client.ic.getDutInfo(ctx, req)
logging.Infof(ctx, "[v2] GetDutInfo result: %#v: %s", req, err)
return dut, now, err
}
return client.gc.getDutInfo(ctx, req)
}
func (client *duoClient) deviceConfigsExists(ctx context.Context, configIds []*device.ConfigId) (map[int32]bool, error) {
return client.ic.deviceConfigsExists(ctx, configIds)
}