blob: 26c472bf90d973a6625579258cbc2a173d715ffc [file]
// Copyright 2025 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validate
import (
"errors"
"fmt"
"github.com/anishathalye/porcupine"
"go.uber.org/zap"
)
type RobustnessResult struct {
Assumptions Result
Linearization LinearizationResult
Watch Result
Serializable Result
}
type Result struct {
Status ResultStatus
Message string
}
type ResultStatus string
var (
Unknown ResultStatus
Success ResultStatus = "Success"
Failure ResultStatus = "Failure"
Timeout ResultStatus = "Timeout"
)
func (r RobustnessResult) Error() error {
if err := r.Assumptions.Error(); err != nil {
return fmt.Errorf("assumptions: %w", err)
}
if err := r.Linearization.Error(); err != nil {
return fmt.Errorf("linearization: %w", err)
}
if err := r.Watch.Error(); err != nil {
return fmt.Errorf("watch: %w", err)
}
if err := r.Serializable.Error(); err != nil {
return fmt.Errorf("serializable: %w", err)
}
return nil
}
func ResultFromError(err error) Result {
if err != nil {
return Result{
Status: Failure,
Message: err.Error(),
}
}
return Result{
Status: Success,
}
}
func (r Result) Error() error {
switch r.Status {
case Success, Unknown:
return nil
default:
return errors.New(r.String())
}
}
func (r Result) String() string {
if r.Message != "" {
return fmt.Sprintf("%s: %s", r.Status, r.Message)
}
return string(r.Status)
}
type LinearizationResult struct {
Info porcupine.LinearizationInfo
Model porcupine.Model
Result
}
func (r *LinearizationResult) Visualize(lg *zap.Logger, path string) error {
if r.Model.Step == nil && r.Model.StepContext == nil {
lg.Info("Skipping linearization visualization as model is not initialized")
return nil
}
err := r.Error()
if err != nil {
lg.Info("Skipping linearization visualization", zap.Error(err))
return nil
}
lg.Info("Saving visualization", zap.String("path", path))
err = porcupine.VisualizePath(r.Model, r.Info, path)
if err != nil {
return fmt.Errorf("failed to visualize, err: %w", err)
}
return nil
}
func (r *LinearizationResult) AddToVisualization(serializable []porcupine.Operation) {
annotations := []porcupine.Annotation{}
for _, op := range serializable {
annotations = append(annotations, porcupine.Annotation{
ClientId: op.ClientId,
Start: op.Call,
End: op.Return,
Description: r.Model.DescribeOperation(op.Input, op.Output),
Details: r.Model.DescribeOperationMetadata(op.Metadata),
})
}
r.Info.AddAnnotations(annotations)
}