github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/protectedts/ptstorage/storage.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Package ptstorage implements protectedts.Storage.
    12  package ptstorage
    13  
    14  import (
    15  	"context"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/kv"
    18  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts"
    19  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts/ptpb"
    20  	"github.com/cockroachdb/cockroach/pkg/security"
    21  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sqlutil"
    25  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    26  	"github.com/cockroachdb/cockroach/pkg/util/log"
    27  	"github.com/cockroachdb/cockroach/pkg/util/protoutil"
    28  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    29  	"github.com/cockroachdb/errors"
    30  )
    31  
    32  // TODO(ajwerner): Consider memory accounting.
    33  // TODO(ajwerner): Add metrics.
    34  
    35  // TODO(ajwerner): Provide some sort of reconciliation of metadata in the face
    36  // of corruption. Not clear how or why such corruption might happen but if it
    37  // does it might be nice to have an escape hatch. Perhaps another interface
    38  // method which scans the records and updates the counts in the meta row
    39  // accordingly.
    40  
    41  // TODO(ajwerner): Hook into the alerts infrastructure and metrics to provide
    42  // visibility into corruption when it is detected.
    43  
    44  // storage interacts with the durable state of the protectedts subsystem.
    45  type storage struct {
    46  	settings *cluster.Settings
    47  	ex       sqlutil.InternalExecutor
    48  }
    49  
    50  var _ protectedts.Storage = (*storage)(nil)
    51  
    52  // New creates a new Storage.
    53  func New(settings *cluster.Settings, ex sqlutil.InternalExecutor) protectedts.Storage {
    54  	return &storage{settings: settings, ex: ex}
    55  }
    56  
    57  var errNoTxn = errors.New("must provide a non-nil transaction")
    58  
    59  func (p *storage) Protect(ctx context.Context, txn *kv.Txn, r *ptpb.Record) error {
    60  	if err := validateRecordForProtect(r); err != nil {
    61  		return err
    62  	}
    63  	if txn == nil {
    64  		return errNoTxn
    65  	}
    66  	encodedSpans, err := protoutil.Marshal(&Spans{Spans: r.Spans})
    67  	if err != nil { // how can this possibly fail?
    68  		return errors.Wrap(err, "failed to marshal spans")
    69  	}
    70  	meta := r.Meta
    71  	if meta == nil {
    72  		// v20.1 crashes in rowToRecord and storage.Release if it finds a NULL
    73  		// value in system.protected_ts_records.meta. v20.2 and above handle
    74  		// this correctly, but we need to maintain mixed version compatibility
    75  		// for at least one release.
    76  		// TODO(nvanbenschoten): remove this for v21.1.
    77  		meta = []byte{}
    78  	}
    79  	s := makeSettings(p.settings)
    80  	rows, err := p.ex.QueryEx(ctx, "protectedts-protect", txn,
    81  		sqlbase.InternalExecutorSessionDataOverride{User: security.NodeUser},
    82  		protectQuery,
    83  		s.maxSpans, s.maxBytes, len(r.Spans),
    84  		r.ID.GetBytesMut(), r.Timestamp.AsOfSystemTime(),
    85  		r.MetaType, meta,
    86  		len(r.Spans), encodedSpans)
    87  	if err != nil {
    88  		return errors.Wrapf(err, "failed to write record %v", r.ID)
    89  	}
    90  	row := rows[0]
    91  	if failed := *row[0].(*tree.DBool); failed {
    92  		curNumSpans := int64(*row[1].(*tree.DInt))
    93  		if curNumSpans+int64(len(r.Spans)) > s.maxSpans {
    94  			return errors.Errorf("protectedts: limit exceeded: %d+%d > %d spans",
    95  				curNumSpans, len(r.Spans), s.maxSpans)
    96  		}
    97  		curBytes := int64(*row[2].(*tree.DInt))
    98  		recordBytes := int64(len(encodedSpans) + len(r.Meta) + len(r.MetaType))
    99  		if curBytes+recordBytes > s.maxBytes {
   100  			return errors.Errorf("protectedts: limit exceeded: %d+%d > %d bytes",
   101  				curBytes, recordBytes, s.maxBytes)
   102  		}
   103  		return protectedts.ErrExists
   104  	}
   105  	return nil
   106  }
   107  
   108  func (p *storage) GetRecord(ctx context.Context, txn *kv.Txn, id uuid.UUID) (*ptpb.Record, error) {
   109  	if txn == nil {
   110  		return nil, errNoTxn
   111  	}
   112  	row, err := p.ex.QueryRowEx(ctx, "protectedts-GetRecord", txn,
   113  		sqlbase.InternalExecutorSessionDataOverride{User: security.NodeUser},
   114  		getRecordQuery, id.GetBytesMut())
   115  	if err != nil {
   116  		return nil, errors.Wrapf(err, "failed to read record %v", id)
   117  	}
   118  	if len(row) == 0 {
   119  		return nil, protectedts.ErrNotExists
   120  	}
   121  	var r ptpb.Record
   122  	if err := rowToRecord(ctx, row, &r); err != nil {
   123  		return nil, err
   124  	}
   125  	return &r, nil
   126  }
   127  
   128  func (p *storage) MarkVerified(ctx context.Context, txn *kv.Txn, id uuid.UUID) error {
   129  	if txn == nil {
   130  		return errNoTxn
   131  	}
   132  	rows, err := p.ex.QueryEx(ctx, "protectedts-MarkVerified", txn,
   133  		sqlbase.InternalExecutorSessionDataOverride{User: security.NodeUser},
   134  		markVerifiedQuery, id.GetBytesMut())
   135  	if err != nil {
   136  		return errors.Wrapf(err, "failed to mark record %v as verified", id)
   137  	}
   138  	if len(rows) == 0 {
   139  		return protectedts.ErrNotExists
   140  	}
   141  	return nil
   142  }
   143  
   144  func (p *storage) Release(ctx context.Context, txn *kv.Txn, id uuid.UUID) error {
   145  	if txn == nil {
   146  		return errNoTxn
   147  	}
   148  	rows, err := p.ex.QueryEx(ctx, "protectedts-Release", txn,
   149  		sqlbase.InternalExecutorSessionDataOverride{User: security.NodeUser},
   150  		releaseQuery, id.GetBytesMut())
   151  	if err != nil {
   152  		return errors.Wrapf(err, "failed to release record %v", id)
   153  	}
   154  	if len(rows) == 0 {
   155  		return protectedts.ErrNotExists
   156  	}
   157  	return nil
   158  }
   159  
   160  func (p *storage) GetMetadata(ctx context.Context, txn *kv.Txn) (ptpb.Metadata, error) {
   161  	if txn == nil {
   162  		return ptpb.Metadata{}, errNoTxn
   163  	}
   164  	row, err := p.ex.QueryRowEx(ctx, "protectedts-GetMetadata", txn,
   165  		sqlbase.InternalExecutorSessionDataOverride{User: security.NodeUser},
   166  		getMetadataQuery)
   167  	if err != nil {
   168  		return ptpb.Metadata{}, errors.Wrap(err, "failed to read metadata")
   169  	}
   170  	return ptpb.Metadata{
   171  		Version:    uint64(*row[0].(*tree.DInt)),
   172  		NumRecords: uint64(*row[1].(*tree.DInt)),
   173  		NumSpans:   uint64(*row[2].(*tree.DInt)),
   174  		TotalBytes: uint64(*row[3].(*tree.DInt)),
   175  	}, nil
   176  }
   177  
   178  func (p *storage) GetState(ctx context.Context, txn *kv.Txn) (ptpb.State, error) {
   179  	if txn == nil {
   180  		return ptpb.State{}, errNoTxn
   181  	}
   182  	md, err := p.GetMetadata(ctx, txn)
   183  	if err != nil {
   184  		return ptpb.State{}, err
   185  	}
   186  	records, err := p.getRecords(ctx, txn)
   187  	if err != nil {
   188  		return ptpb.State{}, err
   189  	}
   190  	return ptpb.State{
   191  		Metadata: md,
   192  		Records:  records,
   193  	}, nil
   194  }
   195  
   196  func (p *storage) getRecords(ctx context.Context, txn *kv.Txn) ([]ptpb.Record, error) {
   197  	rows, err := p.ex.QueryEx(ctx, "protectedts-GetRecords", txn,
   198  		sqlbase.InternalExecutorSessionDataOverride{User: security.NodeUser},
   199  		getRecordsQuery)
   200  	if err != nil {
   201  		return nil, errors.Wrap(err, "failed to read records")
   202  	}
   203  	if len(rows) == 0 {
   204  		return nil, nil
   205  	}
   206  	records := make([]ptpb.Record, len(rows))
   207  	for i, row := range rows {
   208  		if err := rowToRecord(ctx, row, &records[i]); err != nil {
   209  			log.Errorf(ctx, "failed to parse row as record: %v", err)
   210  		}
   211  	}
   212  	return records, nil
   213  }
   214  
   215  // rowToRecord parses a row as returned from the variants of getRecords and
   216  // populates the passed *Record. If any errors are encountered during parsing,
   217  // they are logged but not returned. Returning an error due to malformed data
   218  // in the protected timestamp subsystem would create more problems than it would
   219  // solve. Malformed records can still be removed (and hopefully will be).
   220  func rowToRecord(ctx context.Context, row tree.Datums, r *ptpb.Record) error {
   221  	r.ID = row[0].(*tree.DUuid).UUID
   222  	tsDecimal := row[1].(*tree.DDecimal)
   223  	ts, err := tree.DecimalToHLC(&tsDecimal.Decimal)
   224  	if err != nil {
   225  		return errors.Wrapf(err, "failed to parse timestamp for %v", r.ID)
   226  	}
   227  	r.Timestamp = ts
   228  
   229  	r.MetaType = string(*row[2].(*tree.DString))
   230  	if row[3] != tree.DNull {
   231  		if meta := row[3].(*tree.DBytes); len(*meta) > 0 {
   232  			r.Meta = []byte(*meta)
   233  		}
   234  	}
   235  	var spans Spans
   236  	if err := protoutil.Unmarshal([]byte(*row[4].(*tree.DBytes)), &spans); err != nil {
   237  		return errors.Wrapf(err, "failed to unmarshal spans for %v", r.ID)
   238  	}
   239  	r.Spans = spans.Spans
   240  	r.Verified = bool(*row[5].(*tree.DBool))
   241  	return nil
   242  }
   243  
   244  type settings struct {
   245  	maxSpans int64
   246  	maxBytes int64
   247  }
   248  
   249  func makeSettings(s *cluster.Settings) settings {
   250  	return settings{
   251  		maxSpans: protectedts.MaxSpans.Get(&s.SV),
   252  		maxBytes: protectedts.MaxBytes.Get(&s.SV),
   253  	}
   254  }
   255  
   256  var (
   257  	errZeroTimestamp        = errors.New("invalid zero value timestamp")
   258  	errZeroID               = errors.New("invalid zero value ID")
   259  	errEmptySpans           = errors.Errorf("invalid empty set of spans")
   260  	errInvalidMeta          = errors.Errorf("invalid Meta with empty MetaType")
   261  	errCreateVerifiedRecord = errors.Errorf("cannot create a verified record")
   262  )
   263  
   264  func validateRecordForProtect(r *ptpb.Record) error {
   265  	if r.Timestamp == (hlc.Timestamp{}) {
   266  		return errZeroTimestamp
   267  	}
   268  	if r.ID == uuid.Nil {
   269  		return errZeroID
   270  	}
   271  	if len(r.Spans) == 0 {
   272  		return errEmptySpans
   273  	}
   274  	if len(r.Meta) > 0 && len(r.MetaType) == 0 {
   275  		return errInvalidMeta
   276  	}
   277  	if r.Verified {
   278  		return errCreateVerifiedRecord
   279  	}
   280  	return nil
   281  }