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 }