github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/automation/run/store.go (about)

     1  package run
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"sort"
     9  	"sync"
    10  
    11  	"github.com/qri-io/qri/automation/workflow"
    12  	"github.com/qri-io/qri/base/params"
    13  	"github.com/qri-io/qri/event"
    14  	"github.com/qri-io/qri/profile"
    15  )
    16  
    17  var (
    18  	// ErrNotFound indicates that the run.State was not found in the Store
    19  	ErrNotFound = fmt.Errorf("run not found")
    20  	// ErrUnknownWorkflowID indicates that the given workflow.ID has no
    21  	// associated run.State in the Store
    22  	ErrUnknownWorkflowID = fmt.Errorf("unknown workflow ID")
    23  )
    24  
    25  // Store stores and updates run.States. It is also responsible for tracking the
    26  // number of times a workflow has run, as well as listening for transform
    27  // events and updating the associated the run.State accordingly
    28  type Store interface {
    29  	// Create adds a new run State to the Store
    30  	Create(ctx context.Context, r *State) (*State, error)
    31  	// Put puts a run State with an existing run ID into the Store
    32  	Put(ctx context.Context, r *State) (*State, error)
    33  	// Get gets the associated run.State
    34  	Get(ctx context.Context, id string) (*State, error)
    35  	// Count returns the number of runs for a given workflow.ID
    36  	Count(ctx context.Context, wid workflow.ID) (int, error)
    37  	// List lists all the runs associated with the workflow.ID in reverse
    38  	// chronological order
    39  	List(ctx context.Context, wid workflow.ID, lp params.List) ([]*State, error)
    40  	// GetLatest returns the most recent run associated with the workflow id
    41  	GetLatest(ctx context.Context, wid workflow.ID) (*State, error)
    42  	// GetStatus returns the status of the latest run based on the
    43  	// workflow.ID
    44  	GetStatus(ctx context.Context, wid workflow.ID) (Status, error)
    45  	// ListByStatus returns a list of run.State entries with a given status
    46  	// looking only at the most recent run of each Workflow
    47  	ListByStatus(ctx context.Context, owner profile.ID, s Status, lp params.List) ([]*State, error)
    48  	// Shutdown closes the store
    49  	Shutdown() error
    50  }
    51  
    52  // EventAdder is an extension interface that optimizes writing an event to a run
    53  // Result should equal to calling:
    54  //   run := store.Get(id)
    55  //   run.AddTransformEvent(e)
    56  //   store.Put(run)
    57  type EventAdder interface {
    58  	Store
    59  	// AddEvent writes an event to the store, attaching it to an existing stored
    60  	// run state
    61  	AddEvent(id string, e event.Event) error
    62  }
    63  
    64  // MemStore is an in memory representation of a Store
    65  type MemStore struct {
    66  	mu        sync.Mutex
    67  	workflows map[workflow.ID]*workflowMeta
    68  	runs      map[string]*State
    69  }
    70  
    71  type workflowMeta struct {
    72  	Count  int      `json:"count"`
    73  	RunIDs []string `json:"runIDs"`
    74  }
    75  
    76  func newWorkflowMeta() *workflowMeta {
    77  	return &workflowMeta{
    78  		Count:  0,
    79  		RunIDs: []string{},
    80  	}
    81  }
    82  
    83  var (
    84  	_ Store      = (*MemStore)(nil)
    85  	_ EventAdder = (*MemStore)(nil)
    86  )
    87  
    88  // NewMemStore returns a MemStore
    89  func NewMemStore() *MemStore {
    90  	s := &MemStore{
    91  		workflows: map[workflow.ID]*workflowMeta{},
    92  		runs:      map[string]*State{},
    93  	}
    94  	return s
    95  }
    96  
    97  // Create adds a new run.State to a MemStore
    98  func (s *MemStore) Create(ctx context.Context, r *State) (*State, error) {
    99  	if r == nil {
   100  		return nil, fmt.Errorf("run is nil")
   101  	}
   102  	run := r.Copy()
   103  	if run.ID == "" {
   104  		run.ID = NewID()
   105  	}
   106  	if _, err := s.Get(ctx, run.ID); !errors.Is(err, ErrNotFound) {
   107  		return nil, fmt.Errorf("run with this ID already exists")
   108  	}
   109  	if err := run.Validate(); err != nil {
   110  		return nil, err
   111  	}
   112  	s.mu.Lock()
   113  	defer s.mu.Unlock()
   114  	_, ok := s.workflows[run.WorkflowID]
   115  	if !ok {
   116  		wfm := newWorkflowMeta()
   117  		s.workflows[run.WorkflowID] = wfm
   118  	}
   119  	s.workflows[run.WorkflowID].Count++
   120  	runIDs := s.workflows[run.WorkflowID].RunIDs
   121  	s.workflows[run.WorkflowID].RunIDs = append(runIDs, run.ID)
   122  	s.runs[run.ID] = run
   123  	return run, nil
   124  }
   125  
   126  // Put updates an existing run.State in a MemStore
   127  func (s *MemStore) Put(ctx context.Context, r *State) (*State, error) {
   128  	if r == nil {
   129  		return nil, fmt.Errorf("run is nil")
   130  	}
   131  	run := r.Copy()
   132  	if run.ID == "" {
   133  		return nil, fmt.Errorf("run has empty ID")
   134  	}
   135  	fetchedR, err := s.Get(ctx, run.ID)
   136  	if err != nil {
   137  		return nil, ErrNotFound
   138  	}
   139  	if fetchedR.WorkflowID != run.WorkflowID {
   140  		return nil, fmt.Errorf("run.State's WorkflowID does not match the WorkflowID of the associated run.State currently in the store")
   141  	}
   142  	if err := run.Validate(); err != nil {
   143  		return nil, err
   144  	}
   145  	s.mu.Lock()
   146  	defer s.mu.Unlock()
   147  	s.runs[run.ID] = run
   148  	return run, nil
   149  }
   150  
   151  // Get fetches a run.State using the associated ID
   152  func (s *MemStore) Get(ctx context.Context, id string) (*State, error) {
   153  	s.mu.Lock()
   154  	r, ok := s.runs[id]
   155  	s.mu.Unlock()
   156  	if !ok {
   157  		return nil, ErrNotFound
   158  	}
   159  	return r, nil
   160  }
   161  
   162  // Count returns the number of runs for a given workflow.ID
   163  func (s *MemStore) Count(ctx context.Context, wid workflow.ID) (int, error) {
   164  	s.mu.Lock()
   165  	defer s.mu.Unlock()
   166  	wfm, ok := s.workflows[wid]
   167  	if !ok {
   168  		return 0, fmt.Errorf("%w %q", ErrUnknownWorkflowID, wid)
   169  	}
   170  	return wfm.Count, nil
   171  }
   172  
   173  // List lists all the runs associated with the workflow.ID in reverse
   174  // chronological order
   175  func (s *MemStore) List(ctx context.Context, wid workflow.ID, lp params.List) ([]*State, error) {
   176  	if err := lp.Validate(); err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	s.mu.Lock()
   181  	defer s.mu.Unlock()
   182  	wfm, ok := s.workflows[wid]
   183  	if !ok {
   184  		return nil, fmt.Errorf("%w %q", ErrUnknownWorkflowID, wid)
   185  	}
   186  	runIDs := wfm.RunIDs
   187  	runs := []*State{}
   188  	for i := len(runIDs) - 1; i >= 0; i-- {
   189  		id := runIDs[i]
   190  		run, ok := s.runs[id]
   191  		if !ok {
   192  			return nil, fmt.Errorf("run %q missing from the store", id)
   193  		}
   194  		runs = append(runs, run)
   195  	}
   196  
   197  	if lp.Offset >= len(runs) {
   198  		return []*State{}, nil
   199  	}
   200  
   201  	start := lp.Offset
   202  	end := lp.Offset + lp.Limit
   203  	if end > len(runs) || lp.All() {
   204  		end = len(runs)
   205  	}
   206  	return runs[start:end], nil
   207  }
   208  
   209  // GetLatest returns the most recent run associated with the workflow id
   210  func (s *MemStore) GetLatest(ctx context.Context, wid workflow.ID) (*State, error) {
   211  	s.mu.Lock()
   212  	defer s.mu.Unlock()
   213  
   214  	wfm, ok := s.workflows[wid]
   215  	if !ok {
   216  		return nil, fmt.Errorf("%w %q", ErrUnknownWorkflowID, wid)
   217  	}
   218  	runIDs := wfm.RunIDs
   219  	latestRunID := runIDs[len(runIDs)-1]
   220  	run, ok := s.runs[latestRunID]
   221  	if !ok {
   222  		return nil, fmt.Errorf("run %q missing from the store", latestRunID)
   223  	}
   224  	return run, nil
   225  }
   226  
   227  // GetStatus returns the status of the latest run based on the
   228  // workflow.ID
   229  func (s *MemStore) GetStatus(ctx context.Context, wid workflow.ID) (Status, error) {
   230  	run, err := s.GetLatest(ctx, wid)
   231  	if err != nil {
   232  		return "", err
   233  	}
   234  	return run.Status, nil
   235  }
   236  
   237  // ListByStatus returns a list of run.State entries with a given status
   238  // looking only at the most recent run of each Workflow
   239  func (s *MemStore) ListByStatus(ctx context.Context, owner profile.ID, status Status, lp params.List) ([]*State, error) {
   240  	if err := lp.Validate(); err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	set := NewSet()
   245  	s.mu.Lock()
   246  	defer s.mu.Unlock()
   247  	for _, wfm := range s.workflows {
   248  		runIDs := wfm.RunIDs
   249  		rid := runIDs[len(runIDs)-1]
   250  		run, ok := s.runs[rid]
   251  		if !ok {
   252  			return nil, fmt.Errorf("run %q missing from the store", rid)
   253  		}
   254  		if run.Status == status {
   255  			set.Add(run)
   256  		}
   257  	}
   258  
   259  	if lp.Offset >= set.Len() {
   260  		return []*State{}, nil
   261  	}
   262  
   263  	start := lp.Offset
   264  	end := lp.Offset + lp.Limit
   265  	if end > set.Len() || lp.All() {
   266  		end = set.Len()
   267  	}
   268  
   269  	sort.Sort(set)
   270  	return set.Slice(start, end), nil
   271  }
   272  
   273  // Shutdown closes the store
   274  func (s *MemStore) Shutdown() error {
   275  	return nil
   276  }
   277  
   278  // AddEvent writes an event to the store, attaching it to an existing stored
   279  // run state
   280  func (s *MemStore) AddEvent(id string, e event.Event) error {
   281  	s.mu.Lock()
   282  	defer s.mu.Unlock()
   283  
   284  	run, ok := s.runs[id]
   285  	if !ok {
   286  		return ErrNotFound
   287  	}
   288  	if err := run.AddTransformEvent(e); err != nil {
   289  		return fmt.Errorf("adding transform event to run: %w", err)
   290  	}
   291  
   292  	s.runs[id] = run
   293  	return nil
   294  }
   295  
   296  // MarshalJSON satisfies the json.Marshaller interface
   297  func (s *MemStore) MarshalJSON() ([]byte, error) {
   298  	if s == nil {
   299  		s = NewMemStore()
   300  	}
   301  	return json.Marshal(struct {
   302  		Workflows map[workflow.ID]*workflowMeta `json:"workflows"`
   303  		Runs      map[string]*State             `json:"runs"`
   304  	}{Workflows: s.workflows, Runs: s.runs})
   305  }
   306  
   307  // UnmarshalJSON satisfies the json.Unmarshaller interface
   308  func (s *MemStore) UnmarshalJSON(p []byte) error {
   309  	v := struct {
   310  		Workflows map[workflow.ID]*workflowMeta `json:"workflows"`
   311  		Runs      map[string]*State             `json:"runs"`
   312  	}{}
   313  
   314  	if err := json.Unmarshal(p, &v); err != nil {
   315  		return err
   316  	}
   317  	s.workflows = v.Workflows
   318  	s.runs = v.Runs
   319  	return nil
   320  }