blob: cc144fcd959252abe84c3f059cc92fcaed0cddf1 [file] [log] [blame]
package diffstore
import (
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/diff"
"go.skia.org/infra/golden/go/diffstore/common"
"go.skia.org/infra/golden/go/types"
"google.golang.org/grpc"
)
// NetDiffStore implements the DiffStore interface and wraps around
// a DiffService client.
type NetDiffStore struct {
// serviceClient is the gRPC client for the DiffService.
serviceClient DiffServiceClient
// diffServerImageAddress is the port where the diff server serves images.
diffServerImageAddress string
// codec is used to decode the byte array received from the diff server into
// a diff metrics map
codec util.Codec
}
// NewNetDiffStore implements the diff.DiffStore interface via the gRPC-based DiffService.
func NewNetDiffStore(conn *grpc.ClientConn, diffServerImageAddress string) (diff.DiffStore, error) {
serviceClient := NewDiffServiceClient(conn)
if _, err := serviceClient.Ping(context.Background(), &Empty{}); err != nil {
return nil, skerr.Wrapf(err, "pinging connection to %s", diffServerImageAddress)
}
return &NetDiffStore{
serviceClient: serviceClient,
diffServerImageAddress: diffServerImageAddress,
codec: util.NewJSONCodec(map[types.Digest]*diff.DiffMetrics{}),
}, nil
}
// NewForTesting returns an instantiated NetDiffStore. It should only be used by tests which are
// mocking DiffServiceClient
func NewForTesting(msc DiffServiceClient, diffServerImageAddress string) diff.DiffStore {
return &NetDiffStore{
serviceClient: msc,
diffServerImageAddress: diffServerImageAddress,
codec: util.NewJSONCodec(map[types.Digest]*diff.DiffMetrics{}),
}
}
// Get implements the diff.DiffStore interface.
func (n *NetDiffStore) Get(ctx context.Context, mainDigest types.Digest, rightDigests types.DigestSlice) (map[types.Digest]*diff.DiffMetrics, error) {
req := &GetDiffsRequest{MainDigest: string(mainDigest), RightDigests: common.AsStrings(rightDigests)}
resp, err := n.serviceClient.GetDiffs(ctx, req)
if err != nil {
return nil, skerr.Wrap(err)
}
data, err := n.codec.Decode(resp.Diffs)
if err != nil {
return nil, skerr.Wrapf(err, "Could not decode response")
}
diffMetrics, ok := data.(map[types.Digest]*diff.DiffMetrics)
if !ok {
return nil, skerr.Fmt("Not a valid diff metrics: %#v", data)
}
return diffMetrics, nil
}
// ImageHandler implements the diff.DiffStore interface. The images are expected to be served
// by the server that implements the backend of the DiffService.
func (n *NetDiffStore) ImageHandler(urlPrefix string) (http.Handler, error) {
// Set up a proxy to the diffserver images ports.
// With ingress rules, it would be possible to serve directly to the diffserver
// and bypass the main server.
targetURL, err := url.Parse(fmt.Sprintf("http://%s", n.diffServerImageAddress))
if err != nil {
return nil, skerr.Wrapf(err, "invalid URL for serving diff images")
}
return httputil.NewSingleHostReverseProxy(targetURL), nil
}
// UnavailableDigests implements the diff.DiffStore interface.
func (n *NetDiffStore) UnavailableDigests(ctx context.Context) (map[types.Digest]*diff.DigestFailure, error) {
resp, err := n.serviceClient.UnavailableDigests(ctx, &Empty{})
if err != nil {
return nil, skerr.Wrap(err)
}
ret := make(map[types.Digest]*diff.DigestFailure, len(resp.DigestFailures))
for k, failure := range resp.DigestFailures {
ret[types.Digest(k)] = &diff.DigestFailure{
Digest: types.Digest(failure.Digest),
Reason: diff.DiffErr(failure.Reason),
TS: failure.TS,
}
}
return ret, nil
}
// PurgeDigests implements the the diff.DiffStore interface.
func (n *NetDiffStore) PurgeDigests(ctx context.Context, digests types.DigestSlice, purgeGCS bool) error {
req := &PurgeDigestsRequest{Digests: common.AsStrings(digests), PurgeGCS: purgeGCS}
_, err := n.serviceClient.PurgeDigests(ctx, req)
if err != nil {
return skerr.Wrapf(err, "purging %d digests %s", len(digests), digests)
}
return nil
}