blob: 8a989bd8a4f53954ab6dc266068a42a656343cd4 [file] [log] [blame]
package tilo
import (
"context"
"errors"
"fmt"
"time"
"fuchsia.googlesource.com/infra/infra/tilo/resultstore"
"github.com/google/uuid"
)
var (
errMissingInvocationID = errors.New("Context is missing invocation ID")
errMissingInvocationName = errors.New("Context is missing invocation name")
)
// Start creates a new Invocation in ResultStore and returns new Logger for modifying the
// Invocation. Host is the hostname of the ResultStore backend.
//
// TODO(IN-699): Define constants for the ResultStore backends and document them here.
func Start(ctx context.Context, environment resultstore.Environment) (Logger, error) {
// Connect to ResultStore.
svc, err := resultstore.Connect(ctx, environment)
if err != nil {
return nil, err
}
// Create the Invocation.
authToken := uuid.New().String()
ctx, err = resultstore.SetAuthToken(ctx, authToken)
if err != nil {
return nil, err
}
invocation, err := svc.CreateInvocation(ctx, &resultstore.Invocation{
ID: uuid.New().String(),
StartTime: time.Now(),
})
if err != nil {
return nil, err
}
// Initialize the resultstore.Context.
con := resultstore.NewContext()
con.SetEnvironment(environment)
con.SetAuthToken(authToken)
con.SetInvocationID(invocation.ID)
con.SetInvocationName(invocation.Name)
return &logger{con: con, svc: svc}, nil
}
// Resume creates a new Logger for the Invocation identified by a resultstore.Context.
func Resume(ctx context.Context, con resultstore.Context) (Logger, error) {
svc, err := resultstore.Connect(ctx, con.Environment())
if err != nil {
return nil, err
}
return &logger{con: con, svc: svc}, nil
}
// Logger is an interface for recording Fuchsia test events in ResultStore.
type Logger interface {
// Context returns the context associated with the current invocation. One Logger can
// continue editing this Logger's Invocation by inheriting its Context.
Context() resultstore.Context
// LogEnvironment reports that a test Environment has been discovered. The
// environment is logged in ResultStore as a Configuration.
LogEnvironment(context.Context, EnvironmentFoundEvent) error
// LogTestFound reports that a test has been discovered. The Test is logged in
// ResultStore as a Target.
LogTestFound(context.Context, TestFoundEvent) error
// LogTestStarted reports that a test has started. The corresponding ResultStore
// target recieves a ConfiguredTarget and Action.
LogTestStarted(context.Context, TestStartedEvent) error
// LogTestFinished reports that a test has finished. The corresponding ResultStore
// Action, ConfiguredTarget, and Target are updated and finished.
LogTestFinished(context.Context, TestFinishedEvent) error
// End finishes the Invocation.
End(context.Context) error
}
// The default Logger implementation
type logger struct {
con resultstore.Context
svc resultstore.Service
}
// NewLogger is Visible for testing. Use `Start` and `Resume` instead.
func NewLogger(svc resultstore.Service, con resultstore.Context) Logger {
return &logger{con: con, svc: svc}
}
func (l *logger) Context() resultstore.Context {
return l.con
}
func (l *logger) LogEnvironment(ctx context.Context, e EnvironmentFoundEvent) error {
return l.createConfiguration(ctx, e)
}
func (l *logger) LogTestFound(ctx context.Context, e TestFoundEvent) error {
return l.createTarget(ctx, e)
}
func (l *logger) LogTestStarted(ctx context.Context, e TestStartedEvent) error {
if err := l.createConfiguredTarget(ctx, e); err != nil {
return err
}
if err := l.createTestAction(ctx, e); err != nil {
return err
}
return l.startTarget(ctx, e)
}
func (l *logger) LogTestFinished(ctx context.Context, e TestFinishedEvent) error {
if err := l.updateTestAction(ctx, e); err != nil {
return err
}
if err := l.updateConfiguredTarget(ctx, e); err != nil {
return err
}
if err := l.finishConfiguredTarget(ctx, e); err != nil {
return err
}
if err := l.updateTarget(ctx, e); err != nil {
return err
}
return l.finishTarget(ctx, e)
}
func (l *logger) End(ctx context.Context) error {
return l.finishInvocation(ctx)
}
func (l *logger) updateTestAction(ctx context.Context, e TestFinishedEvent) error {
actionName := l.con.TestAction(e.TestName)
if actionName == "" {
return fmt.Errorf("no TestAction for test %s", e.TestName)
}
if _, err := l.svc.UpdateTestAction(ctx, e.ToTestAction(actionName)); err != nil {
return err
}
return nil
}
func (l *logger) updateConfiguredTarget(ctx context.Context, e TestFinishedEvent) error {
configuredTargetName := l.con.ConfiguredTarget(e.TestName, e.EnvName)
if configuredTargetName == "" {
return fmt.Errorf("no ConfiguredTarget for test %s in env %s", e.TestName, e.EnvName)
}
configuredTarget := e.ToConfiguredTarget(configuredTargetName)
if _, err := l.svc.UpdateConfiguredTarget(ctx, configuredTarget); err != nil {
return err
}
return nil
}
func (l *logger) finishConfiguredTarget(ctx context.Context, e TestFinishedEvent) error {
configuredTargetName := l.con.ConfiguredTarget(e.TestName, e.EnvName)
return l.svc.FinishConfiguredTarget(ctx, configuredTargetName)
}
func (l *logger) finishTarget(ctx context.Context, e TestFinishedEvent) error {
targetName := l.con.Target(e.TestName)
return l.svc.FinishTarget(ctx, targetName)
}
func (l *logger) updateTarget(ctx context.Context, e TestFinishedEvent) error {
targetName := l.con.Target(e.TestName)
if targetName == "" {
return fmt.Errorf("no Target for test %s in env %s", e.TestName, e.EnvName)
}
target := e.ToTarget(targetName)
if _, err := l.svc.UpdateTarget(ctx, target); err != nil {
return err
}
return nil
}
func (l *logger) startTarget(ctx context.Context, e TestStartedEvent) error {
invocationID := l.con.InvocationID()
if invocationID == "" {
return errMissingInvocationID
}
_, err := l.svc.UpdateTarget(ctx, e.ToTarget(invocationID))
return err
}
func (l *logger) createConfiguration(ctx context.Context, e EnvironmentFoundEvent) error {
invocationID := l.con.InvocationID()
if invocationID == "" {
return errMissingInvocationID
}
invocationName := l.con.InvocationName()
if invocationName == "" {
return errMissingInvocationName
}
_, err := l.svc.CreateConfiguration(ctx, e.ToConfig(l.con.InvocationID()), invocationName)
return err
}
func (l *logger) createTarget(ctx context.Context, e TestFoundEvent) error {
invocationID := l.con.InvocationID()
if invocationID == "" {
return errMissingInvocationID
}
invocationName := l.con.InvocationName()
if invocationName == "" {
return errMissingInvocationName
}
target, err := l.svc.CreateTarget(ctx, e.ToTarget(invocationID), invocationName)
if err != nil {
return err
}
if !l.con.SetTarget(target.ID, target.Name) {
return fmt.Errorf("Target already exists for env %s", e.Name)
}
return nil
}
func (l *logger) createConfiguredTarget(ctx context.Context, e TestStartedEvent) error {
invocationID := l.con.InvocationID()
if invocationID == "" {
return errMissingInvocationID
}
invocationName := l.con.InvocationName()
if invocationName == "" {
return errMissingInvocationName
}
configuredTarget, err := l.svc.CreateConfiguredTarget(ctx, e.ToConfiguredTarget(invocationID), invocationName)
if err != nil {
return err
}
if !l.con.SetConfiguredTarget(configuredTarget.TargetID, configuredTarget.ConfigID, configuredTarget.Name) {
return fmt.Errorf("ConfiguredTarget already exists for env %s", e.EnvName)
}
return nil
}
func (l *logger) createTestAction(ctx context.Context, e TestStartedEvent) error {
invocationID := l.con.InvocationID()
if invocationID == "" {
return errMissingInvocationID
}
configuredTargetName := l.con.ConfiguredTarget(e.TestName, e.EnvName)
action, err := l.svc.CreateTestAction(ctx, e.ToTestAction(invocationID), configuredTargetName)
if err != nil {
return err
}
if !l.con.SetTestAction(action.ID, action.Name) {
return fmt.Errorf("Test Action already exists for test %s", e.TestName)
}
return nil
}
func (l *logger) finishInvocation(ctx context.Context) error {
invocationName := l.con.InvocationName()
if invocationName == "" {
return errMissingInvocationName
}
return l.svc.FinishInvocation(ctx, invocationName)
}