| 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) |
| } |