go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/appengine/coordinator/logStreamState.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package coordinator
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"time"
    22  
    23  	ds "go.chromium.org/luci/gae/service/datastore"
    24  	"go.chromium.org/luci/logdog/common/types"
    25  )
    26  
    27  // ArchivalState describes the archival state of a LogStream.
    28  type ArchivalState int
    29  
    30  const (
    31  	// NotArchived means that the stream is not archived, and that no archival has
    32  	// been tasked.
    33  	NotArchived ArchivalState = iota
    34  	// ArchiveTasked is true if the log stream has an archival tasked, but has
    35  	// not yet been archived.
    36  	ArchiveTasked
    37  	// ArchivedPartial means that the stream is archived, but that some log
    38  	// entries are missing.
    39  	ArchivedPartial
    40  	// ArchivedComplete means that the stream is archived and all log entries are
    41  	// present.
    42  	ArchivedComplete
    43  )
    44  
    45  // Archived returns true if this ArchivalState implies that the log stream is
    46  // archived.
    47  func (as ArchivalState) Archived() bool {
    48  	switch as {
    49  	case ArchivedPartial, ArchivedComplete:
    50  		return true
    51  	default:
    52  		return false
    53  	}
    54  }
    55  
    56  // ArchivalStateKey is the name of the index key for the archival state.
    57  var ArchivalStateKey = "_ArchivalState"
    58  
    59  // LogStreamStateExpiry is the duration after creation that a LogStreamState
    60  // record should persist for.  After this duration it may be deleted.
    61  const LogStreamStateExpiry = 540 * 24 * time.Hour
    62  
    63  // LogStreamState contains the current state of a LogStream.
    64  //
    65  // This structure has additional datastore fields imposed by the
    66  // PropertyLoadSaver.
    67  //   - _Terminated is true if the LogStream has been terminated.
    68  //   - _ArchivePending is true if the LogStream currently has an archive task
    69  //     dispatched.
    70  //   - _ArchivalState is true if the LogStream has been archived.
    71  //
    72  // See services API's LogStreamState message type.
    73  type LogStreamState struct {
    74  	_id int `gae:"$id,1"`
    75  	// Parent is the key of the corresponding LogStream.
    76  	Parent *ds.Key `gae:"$parent"`
    77  
    78  	// Schema is the datastore schema version for this object. This can be used
    79  	// to facilitate schema migrations.
    80  	//
    81  	// The current schema is CurrentSchemaVersion.
    82  	Schema string `gae:",noindex"`
    83  
    84  	// Created is the last time that this state has been created.
    85  	Created time.Time `gae:",noindex"`
    86  	// Updated is the last time that this state has been updated.
    87  	Updated time.Time `gae:",noindex"`
    88  	// ExpireAt is time after which the state will be deleted.
    89  	ExpireAt time.Time `gae:",noindex"`
    90  
    91  	// Secret is the Butler secret value for this stream.
    92  	//
    93  	// This value may only be returned to LogDog services; it is not user-visible.
    94  	Secret []byte `gae:",noindex"`
    95  
    96  	// TerminatedTime is the Coordinator's record of when this log stream was
    97  	// terminated.
    98  	TerminatedTime time.Time `gae:",noindex"`
    99  	// TerminalIndex is the index of the last log entry in the stream.
   100  	//
   101  	// If this is <0, the log stream is either still streaming or has been
   102  	// archived with no log entries.
   103  	TerminalIndex int64 `gae:",noindex"`
   104  
   105  	// ArchiveRetryCount is the number of times this stream has attempted
   106  	// archival.
   107  	ArchiveRetryCount int64
   108  
   109  	// ArchivedTime is the Coordinator's record of when this log stream was
   110  	// archived. If this is non-zero, it means that the log entry has been
   111  	// archived.
   112  	ArchivedTime time.Time `gae:",noindex"`
   113  	// ArchiveLogEntryCount is the number of LogEntry records that were archived
   114  	// for this log stream.
   115  	//
   116  	// This is valid only if the log stream is Archived.
   117  	ArchiveLogEntryCount int64 `gae:",noindex"`
   118  	// ArchivalKey is the archival key for this log stream. This is used to
   119  	// differentiate the real archival request from those that were dispatched,
   120  	// but that ultimately failed to update state.
   121  	//
   122  	// See createArchivalKey for details on its generation and usage.
   123  	ArchivalKey []byte `gae:",noindex"`
   124  
   125  	// ArchiveIndexURL is the Google Storage URL where the log stream's index is
   126  	// archived.
   127  	ArchiveIndexURL string `gae:",noindex"`
   128  	// ArchiveIndexSize is the size, in bytes, of the archived Index. It will be
   129  	// zero if the file is not archived.
   130  	ArchiveIndexSize int64 `gae:",noindex"`
   131  	// ArchiveStreamURL is the Google Storage URL where the log stream's raw
   132  	// stream data is archived. If this is not empty, the log stream is considered
   133  	// archived.
   134  	ArchiveStreamURL string `gae:",noindex"`
   135  	// ArchiveStreamSize is the size, in bytes, of the archived stream. It will be
   136  	// zero if the file is not archived.
   137  	ArchiveStreamSize int64 `gae:",noindex"`
   138  
   139  	// extra causes datastore to ignore unrecognized fields and strip them in
   140  	// future writes.
   141  	extra ds.PropertyMap `gae:"-,extra"`
   142  }
   143  
   144  // NewLogStreamState returns a LogStreamState with its parent key populated to
   145  // the LogStream with the supplied ID.
   146  func NewLogStreamState(c context.Context, id HashID) *LogStreamState {
   147  	return &LogStreamState{Parent: ds.NewKey(c, "LogStream", string(id), 0, nil)}
   148  }
   149  
   150  // ID returns the LogStream ID for the LogStream that owns this LogStreamState.
   151  func (lst *LogStreamState) ID() HashID {
   152  	return HashID(lst.Parent.StringID())
   153  }
   154  
   155  // Load implements ds.PropertyLoadSaver.
   156  func (lst *LogStreamState) Load(pmap ds.PropertyMap) error {
   157  	// Discard derived properties.
   158  	delete(pmap, "_Terminated")
   159  	delete(pmap, ArchivalStateKey)
   160  
   161  	return ds.GetPLS(lst).Load(pmap)
   162  }
   163  
   164  // Save implements ds.PropertyLoadSaver.
   165  func (lst *LogStreamState) Save(withMeta bool) (ds.PropertyMap, error) {
   166  	lst.Schema = CurrentSchemaVersion
   167  
   168  	// Save default struct fields.
   169  	pmap, err := ds.GetPLS(lst).Save(withMeta)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	pmap["_Terminated"] = ds.MkProperty(lst.Terminated())
   175  	pmap[ArchivalStateKey] = ds.MkProperty(lst.ArchivalState())
   176  
   177  	return pmap, nil
   178  }
   179  
   180  // Validate evaluates the state and data contents of the LogStreamState and
   181  // returns an error if it is invalid.
   182  func (lst *LogStreamState) Validate() error {
   183  	if lst.Created.IsZero() {
   184  		return errors.New("missing created time")
   185  	}
   186  	if lst.Updated.IsZero() {
   187  		return errors.New("missing updated time")
   188  	}
   189  
   190  	if lst.Terminated() && lst.TerminatedTime.IsZero() {
   191  		return errors.New("log stream is terminated, but missing terminated time")
   192  	}
   193  	if err := types.PrefixSecret(lst.Secret).Validate(); err != nil {
   194  		return fmt.Errorf("invalid prefix secret: %v", err)
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  // Terminated returns true if this stream has been terminated.
   201  func (lst *LogStreamState) Terminated() bool {
   202  	if lst.ArchivalState().Archived() {
   203  		return true
   204  	}
   205  	return lst.TerminalIndex >= 0
   206  }
   207  
   208  // ArchivalState returns the archival state of the log stream.
   209  func (lst *LogStreamState) ArchivalState() ArchivalState {
   210  	if lst.ArchivedTime.IsZero() {
   211  		// Not archived, have we dispatched an archival?
   212  		if len(lst.ArchivalKey) > 0 {
   213  			return ArchiveTasked
   214  		}
   215  		return NotArchived
   216  	}
   217  
   218  	if lst.ArchiveLogEntryCount > lst.TerminalIndex {
   219  		return ArchivedComplete
   220  	}
   221  	return ArchivedPartial
   222  }