github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/state/state_store_variables.go (about)

     1  package state
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  
     7  	"github.com/hashicorp/go-memdb"
     8  
     9  	"github.com/hashicorp/nomad/helper"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  )
    12  
    13  // Variables queries all the variables and is used only for
    14  // snapshot/restore and key rotation
    15  func (s *StateStore) Variables(ws memdb.WatchSet) (memdb.ResultIterator, error) {
    16  	txn := s.db.ReadTxn()
    17  
    18  	iter, err := txn.Get(TableVariables, indexID)
    19  	if err != nil {
    20  		return nil, err
    21  	}
    22  
    23  	ws.Add(iter.WatchCh())
    24  	return iter, nil
    25  }
    26  
    27  // GetVariablesByNamespace returns an iterator that contains all
    28  // variables belonging to the provided namespace.
    29  func (s *StateStore) GetVariablesByNamespace(
    30  	ws memdb.WatchSet, namespace string) (memdb.ResultIterator, error) {
    31  	txn := s.db.ReadTxn()
    32  	return s.getVariablesByNamespaceImpl(txn, ws, namespace)
    33  }
    34  
    35  func (s *StateStore) getVariablesByNamespaceImpl(
    36  	txn *txn, ws memdb.WatchSet, namespace string) (memdb.ResultIterator, error) {
    37  	// Walk the entire table.
    38  	iter, err := txn.Get(TableVariables, indexID+"_prefix", namespace, "")
    39  	if err != nil {
    40  		return nil, fmt.Errorf("variable lookup failed: %v", err)
    41  	}
    42  	ws.Add(iter.WatchCh())
    43  
    44  	return iter, nil
    45  }
    46  
    47  // GetVariablesByNamespaceAndPrefix returns an iterator that contains all
    48  // variables belonging to the provided namespace that match the prefix.
    49  func (s *StateStore) GetVariablesByNamespaceAndPrefix(
    50  	ws memdb.WatchSet, namespace, prefix string) (memdb.ResultIterator, error) {
    51  	txn := s.db.ReadTxn()
    52  
    53  	// Walk the entire table.
    54  	iter, err := txn.Get(TableVariables, indexID+"_prefix", namespace, prefix)
    55  	if err != nil {
    56  		return nil, fmt.Errorf("variable lookup failed: %v", err)
    57  	}
    58  	ws.Add(iter.WatchCh())
    59  
    60  	return iter, nil
    61  }
    62  
    63  // GetVariablesByPrefix returns an iterator that contains all variables that
    64  // match the prefix in any namespace. Namespace filtering is the responsibility
    65  // of the caller.
    66  func (s *StateStore) GetVariablesByPrefix(
    67  	ws memdb.WatchSet, prefix string) (memdb.ResultIterator, error) {
    68  	txn := s.db.ReadTxn()
    69  
    70  	// Walk the entire table.
    71  	iter, err := txn.Get(TableVariables, indexPath+"_prefix", prefix)
    72  	if err != nil {
    73  		return nil, fmt.Errorf("variable lookup failed: %v", err)
    74  	}
    75  	ws.Add(iter.WatchCh())
    76  
    77  	return iter, nil
    78  }
    79  
    80  // GetVariablesByKeyID returns an iterator that contains all
    81  // variables that were encrypted with a particular key
    82  func (s *StateStore) GetVariablesByKeyID(
    83  	ws memdb.WatchSet, keyID string) (memdb.ResultIterator, error) {
    84  	txn := s.db.ReadTxn()
    85  
    86  	iter, err := txn.Get(TableVariables, indexKeyID, keyID)
    87  	if err != nil {
    88  		return nil, fmt.Errorf("variable lookup failed: %v", err)
    89  	}
    90  	ws.Add(iter.WatchCh())
    91  
    92  	return iter, nil
    93  }
    94  
    95  // GetVariable returns a single variable at a given namespace and
    96  // path.
    97  func (s *StateStore) GetVariable(
    98  	ws memdb.WatchSet, namespace, path string) (*structs.VariableEncrypted, error) {
    99  	txn := s.db.ReadTxn()
   100  
   101  	// Try to fetch the variable.
   102  	watchCh, raw, err := txn.FirstWatch(TableVariables, indexID, namespace, path)
   103  	if err != nil { // error during fetch
   104  		return nil, fmt.Errorf("variable lookup failed: %v", err)
   105  	}
   106  	ws.Add(watchCh)
   107  	if raw == nil { // not found
   108  		return nil, nil
   109  	}
   110  
   111  	sv := raw.(*structs.VariableEncrypted)
   112  	return sv, nil
   113  }
   114  
   115  // VarSet is used to store a variable object.
   116  func (s *StateStore) VarSet(idx uint64, sv *structs.VarApplyStateRequest) *structs.VarApplyStateResponse {
   117  	tx := s.db.WriteTxn(idx)
   118  	defer tx.Abort()
   119  
   120  	// Perform the actual set.
   121  	resp := s.varSetTxn(tx, idx, sv)
   122  	if resp.IsError() {
   123  		return resp
   124  	}
   125  
   126  	if err := tx.Commit(); err != nil {
   127  		return sv.ErrorResponse(idx, err)
   128  	}
   129  	return resp
   130  }
   131  
   132  // VarSetCAS is used to do a check-and-set operation on a
   133  // variable. The ModifyIndex in the provided entry is used to determine if
   134  // we should write the entry to the state store or not.
   135  func (s *StateStore) VarSetCAS(idx uint64, sv *structs.VarApplyStateRequest) *structs.VarApplyStateResponse {
   136  	tx := s.db.WriteTxn(idx)
   137  	defer tx.Abort()
   138  
   139  	resp := s.varSetCASTxn(tx, idx, sv)
   140  	if resp.IsError() || resp.IsConflict() {
   141  		return resp
   142  	}
   143  
   144  	if err := tx.Commit(); err != nil {
   145  		return sv.ErrorResponse(idx, err)
   146  	}
   147  	return resp
   148  }
   149  
   150  // varSetCASTxn is the inner method used to do a CAS inside an existing
   151  // transaction.
   152  func (s *StateStore) varSetCASTxn(tx WriteTxn, idx uint64, req *structs.VarApplyStateRequest) *structs.VarApplyStateResponse {
   153  	sv := req.Var
   154  	raw, err := tx.First(TableVariables, indexID, sv.Namespace, sv.Path)
   155  	if err != nil {
   156  		return req.ErrorResponse(idx, fmt.Errorf("failed variable lookup: %s", err))
   157  	}
   158  	svEx, ok := raw.(*structs.VariableEncrypted)
   159  
   160  	// ModifyIndex of 0 means that we are doing a set-if-not-exists.
   161  	if sv.ModifyIndex == 0 && raw != nil {
   162  		return req.ConflictResponse(idx, svEx)
   163  	}
   164  
   165  	// If the ModifyIndex is set but the variable doesn't exist, return a
   166  	// plausible zero value as the conflict
   167  	if sv.ModifyIndex != 0 && raw == nil {
   168  		zeroVal := &structs.VariableEncrypted{
   169  			VariableMetadata: structs.VariableMetadata{
   170  				Namespace: sv.Namespace,
   171  				Path:      sv.Path,
   172  			},
   173  		}
   174  		return req.ConflictResponse(idx, zeroVal)
   175  	}
   176  
   177  	// If the existing index does not match the provided CAS index arg, then we
   178  	// shouldn't update anything and can safely return early here.
   179  	if ok && sv.ModifyIndex != svEx.ModifyIndex {
   180  		return req.ConflictResponse(idx, svEx)
   181  	}
   182  
   183  	// If we made it this far, we should perform the set.
   184  	return s.varSetTxn(tx, idx, req)
   185  }
   186  
   187  // varSetTxn is used to insert or update a variable in the state
   188  // store. It is the inner method used and handles only the actual storage.
   189  func (s *StateStore) varSetTxn(tx WriteTxn, idx uint64, req *structs.VarApplyStateRequest) *structs.VarApplyStateResponse {
   190  	sv := req.Var
   191  	existingRaw, err := tx.First(TableVariables, indexID, sv.Namespace, sv.Path)
   192  	if err != nil {
   193  		return req.ErrorResponse(idx, fmt.Errorf("failed sve lookup: %s", err))
   194  	}
   195  	existing, _ := existingRaw.(*structs.VariableEncrypted)
   196  
   197  	existingQuota, err := tx.First(TableVariablesQuotas, indexID, sv.Namespace)
   198  	if err != nil {
   199  		return req.ErrorResponse(idx, fmt.Errorf("variable quota lookup failed: %v", err))
   200  	}
   201  
   202  	var quotaChange int64
   203  
   204  	// Set the CreateIndex and CreateTime
   205  	if existing != nil {
   206  		sv.CreateIndex = existing.CreateIndex
   207  		sv.CreateTime = existing.CreateTime
   208  
   209  		if existing.Equal(*sv) {
   210  			// Skip further writing in the state store if the entry is not actually
   211  			// changed. Nevertheless, the input's ModifyIndex should be reset
   212  			// since the TXN API returns a copy in the response.
   213  			sv.ModifyIndex = existing.ModifyIndex
   214  			sv.ModifyTime = existing.ModifyTime
   215  			return req.SuccessResponse(idx, nil)
   216  		}
   217  		sv.ModifyIndex = idx
   218  		quotaChange = int64(len(sv.Data) - len(existing.Data))
   219  	} else {
   220  		sv.CreateIndex = idx
   221  		sv.ModifyIndex = idx
   222  		quotaChange = int64(len(sv.Data))
   223  	}
   224  
   225  	if err := tx.Insert(TableVariables, sv); err != nil {
   226  		return req.ErrorResponse(idx, fmt.Errorf("failed inserting variable: %s", err))
   227  	}
   228  
   229  	// Track quota usage
   230  	var quotaUsed *structs.VariablesQuota
   231  	if existingQuota != nil {
   232  		quotaUsed = existingQuota.(*structs.VariablesQuota)
   233  		quotaUsed = quotaUsed.Copy()
   234  	} else {
   235  		quotaUsed = &structs.VariablesQuota{
   236  			Namespace:   sv.Namespace,
   237  			CreateIndex: idx,
   238  		}
   239  	}
   240  
   241  	if quotaChange > math.MaxInt64-quotaUsed.Size {
   242  		// this limit is actually shared across all namespaces in the region's
   243  		// quota (if there is one), but we need this check here to prevent
   244  		// overflow as well
   245  		return req.ErrorResponse(idx, fmt.Errorf("variables can store a maximum of %d bytes of encrypted data per namespace", math.MaxInt))
   246  	}
   247  
   248  	if quotaChange > 0 {
   249  		quotaUsed.Size += quotaChange
   250  	} else if quotaChange < 0 {
   251  		quotaUsed.Size -= helper.Min(quotaUsed.Size, -quotaChange)
   252  	}
   253  
   254  	err = s.enforceVariablesQuota(idx, tx, sv.Namespace, quotaChange)
   255  	if err != nil {
   256  		return req.ErrorResponse(idx, err)
   257  	}
   258  
   259  	// we check enforcement above even if there's no change because another
   260  	// namespace may have used up quota to make this no longer valid, but we
   261  	// only update the table if this namespace has changed
   262  	if quotaChange != 0 {
   263  		quotaUsed.ModifyIndex = idx
   264  		if err := tx.Insert(TableVariablesQuotas, quotaUsed); err != nil {
   265  			return req.ErrorResponse(idx, fmt.Errorf("variable quota insert failed: %v", err))
   266  		}
   267  	}
   268  
   269  	if err := tx.Insert(tableIndex,
   270  		&IndexEntry{TableVariables, idx}); err != nil {
   271  		return req.ErrorResponse(idx, fmt.Errorf("failed updating variable index: %s", err))
   272  	}
   273  
   274  	return req.SuccessResponse(idx, &sv.VariableMetadata)
   275  }
   276  
   277  // VarGet is used to retrieve a key/value pair from the state store.
   278  func (s *StateStore) VarGet(ws memdb.WatchSet, namespace, path string) (uint64, *structs.VariableEncrypted, error) {
   279  	tx := s.db.ReadTxn()
   280  	defer tx.Abort()
   281  
   282  	return svGetTxn(tx, ws, namespace, path)
   283  }
   284  
   285  // svGetTxn is the inner method that gets a variable inside an existing
   286  // transaction.
   287  func svGetTxn(tx ReadTxn,
   288  	ws memdb.WatchSet, namespace, path string) (uint64, *structs.VariableEncrypted, error) {
   289  
   290  	// Get the table index.
   291  	idx := svMaxIndex(tx)
   292  
   293  	watchCh, entry, err := tx.FirstWatch(TableVariables, indexID, namespace, path)
   294  	if err != nil {
   295  		return 0, nil, fmt.Errorf("failed variable lookup: %s", err)
   296  	}
   297  	ws.Add(watchCh)
   298  	if entry != nil {
   299  		return idx, entry.(*structs.VariableEncrypted), nil
   300  	}
   301  	return idx, nil, nil
   302  }
   303  
   304  // VarDelete is used to delete a single variable in the
   305  // the state store.
   306  func (s *StateStore) VarDelete(idx uint64, req *structs.VarApplyStateRequest) *structs.VarApplyStateResponse {
   307  	tx := s.db.WriteTxn(idx)
   308  	defer tx.Abort()
   309  
   310  	// Perform the actual delete
   311  	resp := s.svDeleteTxn(tx, idx, req)
   312  	if !resp.IsOk() {
   313  		return resp
   314  	}
   315  
   316  	err := tx.Commit()
   317  	if err != nil {
   318  		return req.ErrorResponse(idx, err)
   319  	}
   320  
   321  	return resp
   322  }
   323  
   324  // VarDeleteCAS is used to conditionally delete a variable if and only if it has
   325  // a given modify index. If the CAS index (cidx) specified is not equal to the
   326  // last observed index for the given variable, then the call is a noop,
   327  // otherwise a normal delete is invoked.
   328  func (s *StateStore) VarDeleteCAS(idx uint64, req *structs.VarApplyStateRequest) *structs.VarApplyStateResponse {
   329  	tx := s.db.WriteTxn(idx)
   330  	defer tx.Abort()
   331  
   332  	resp := s.svDeleteCASTxn(tx, idx, req)
   333  	if !resp.IsOk() {
   334  		return resp
   335  	}
   336  
   337  	err := tx.Commit()
   338  	if err != nil {
   339  		return req.ErrorResponse(idx, err)
   340  	}
   341  
   342  	return resp
   343  }
   344  
   345  // svDeleteCASTxn is an inner method used to check the existing value
   346  // of a variable within an existing transaction as part of a
   347  // conditional delete.
   348  func (s *StateStore) svDeleteCASTxn(tx WriteTxn, idx uint64, req *structs.VarApplyStateRequest) *structs.VarApplyStateResponse {
   349  	sv := req.Var
   350  	raw, err := tx.First(TableVariables, indexID, sv.Namespace, sv.Path)
   351  	if err != nil {
   352  		return req.ErrorResponse(idx, fmt.Errorf("failed variable lookup: %s", err))
   353  	}
   354  
   355  	// ModifyIndex of 0 means that we are doing a delete-if-not-exists, so when
   356  	// raw == nil, it is successful. We should return here without manipulating
   357  	// the state store further.
   358  	if sv.ModifyIndex == 0 && raw == nil {
   359  		return req.SuccessResponse(idx, nil)
   360  	}
   361  
   362  	// If the ModifyIndex is set but the variable doesn't exist, return a
   363  	// plausible zero value as the conflict, because the user _expected_ there
   364  	// to have been a value and its absence is a conflict.
   365  	if sv.ModifyIndex != 0 && raw == nil {
   366  		zeroVal := &structs.VariableEncrypted{
   367  			VariableMetadata: structs.VariableMetadata{
   368  				Namespace: sv.Namespace,
   369  				Path:      sv.Path,
   370  			},
   371  		}
   372  		return req.ConflictResponse(idx, zeroVal)
   373  	}
   374  
   375  	// Any work beyond this point needs to be able to consult the actual
   376  	// returned content, so assert it back into the right type.
   377  	svEx, ok := raw.(*structs.VariableEncrypted)
   378  
   379  	// ModifyIndex of 0 means that we are doing a delete-if-not-exists, but
   380  	// there was a value stored in the state store
   381  	if sv.ModifyIndex == 0 && raw != nil {
   382  		return req.ConflictResponse(idx, svEx)
   383  	}
   384  
   385  	// If the existing index does not match the provided CAS index arg, then we
   386  	// shouldn't update anything and can safely return early here.
   387  	if !ok || sv.ModifyIndex != svEx.ModifyIndex {
   388  		return req.ConflictResponse(idx, svEx)
   389  	}
   390  
   391  	// Call the actual deletion if the above passed.
   392  	return s.svDeleteTxn(tx, idx, req)
   393  }
   394  
   395  // svDeleteTxn is the inner method used to perform the actual deletion
   396  // of a variable within an existing transaction.
   397  func (s *StateStore) svDeleteTxn(tx WriteTxn, idx uint64, req *structs.VarApplyStateRequest) *structs.VarApplyStateResponse {
   398  
   399  	// Look up the entry in the state store.
   400  	existingRaw, err := tx.First(TableVariables, indexID, req.Var.Namespace, req.Var.Path)
   401  	if err != nil {
   402  		return req.ErrorResponse(idx, fmt.Errorf("failed variable lookup: %s", err))
   403  	}
   404  	if existingRaw == nil {
   405  		return req.SuccessResponse(idx, nil)
   406  	}
   407  
   408  	existingQuota, err := tx.First(TableVariablesQuotas, indexID, req.Var.Namespace)
   409  	if err != nil {
   410  		return req.ErrorResponse(idx, fmt.Errorf("variable quota lookup failed: %v", err))
   411  	}
   412  
   413  	sv := existingRaw.(*structs.VariableEncrypted)
   414  
   415  	// Track quota usage
   416  	if existingQuota != nil {
   417  		quotaUsed := existingQuota.(*structs.VariablesQuota)
   418  		quotaUsed = quotaUsed.Copy()
   419  		quotaUsed.Size -= helper.Min(quotaUsed.Size, int64(len(sv.Data)))
   420  		quotaUsed.ModifyIndex = idx
   421  		if err := tx.Insert(TableVariablesQuotas, quotaUsed); err != nil {
   422  			return req.ErrorResponse(idx, fmt.Errorf("variable quota insert failed: %v", err))
   423  		}
   424  	}
   425  
   426  	// Delete the variable and update the index table.
   427  	if err := tx.Delete(TableVariables, sv); err != nil {
   428  		return req.ErrorResponse(idx, fmt.Errorf("failed deleting variable entry: %s", err))
   429  	}
   430  
   431  	if err := tx.Insert(tableIndex, &IndexEntry{TableVariables, idx}); err != nil {
   432  		return req.ErrorResponse(idx, fmt.Errorf("failed updating variable index: %s", err))
   433  	}
   434  
   435  	return req.SuccessResponse(idx, nil)
   436  }
   437  
   438  // This extra indirection is to facilitate the tombstone case if it matters.
   439  func svMaxIndex(tx ReadTxn) uint64 {
   440  	return maxIndexTxn(tx, TableVariables)
   441  }
   442  
   443  // WriteTxn is implemented by memdb.Txn to perform write operations.
   444  type WriteTxn interface {
   445  	ReadTxn
   446  	Defer(func())
   447  	Delete(table string, obj interface{}) error
   448  	DeleteAll(table, index string, args ...interface{}) (int, error)
   449  	DeletePrefix(table string, index string, prefix string) (bool, error)
   450  	Insert(table string, obj interface{}) error
   451  }
   452  
   453  // maxIndex is a helper used to retrieve the highest known index
   454  // amongst a set of tables in the db.
   455  func (s *StateStore) maxIndex(tables ...string) uint64 {
   456  	tx := s.db.ReadTxn()
   457  	defer tx.Abort()
   458  	return maxIndexTxn(tx, tables...)
   459  }
   460  
   461  // maxIndexTxn is a helper used to retrieve the highest known index
   462  // amongst a set of tables in the db.
   463  func maxIndexTxn(tx ReadTxn, tables ...string) uint64 {
   464  	return maxIndexWatchTxn(tx, nil, tables...)
   465  }
   466  
   467  func maxIndexWatchTxn(tx ReadTxn, ws memdb.WatchSet, tables ...string) uint64 {
   468  	var lindex uint64
   469  	for _, table := range tables {
   470  		ch, ti, err := tx.FirstWatch(tableIndex, "id", table)
   471  		if err != nil {
   472  			panic(fmt.Sprintf("unknown index: %s err: %s", table, err))
   473  		}
   474  		if idx, ok := ti.(*IndexEntry); ok && idx.Value > lindex {
   475  			lindex = idx.Value
   476  		}
   477  		ws.Add(ch)
   478  	}
   479  	return lindex
   480  }
   481  
   482  // VariablesQuotas queries all the quotas and is used only for
   483  // snapshot/restore and key rotation
   484  func (s *StateStore) VariablesQuotas(ws memdb.WatchSet) (memdb.ResultIterator, error) {
   485  	txn := s.db.ReadTxn()
   486  
   487  	iter, err := txn.Get(TableVariablesQuotas, indexID)
   488  	if err != nil {
   489  		return nil, err
   490  	}
   491  
   492  	ws.Add(iter.WatchCh())
   493  	return iter, nil
   494  }
   495  
   496  // VariablesQuotaByNamespace queries for quotas for a particular namespace
   497  func (s *StateStore) VariablesQuotaByNamespace(ws memdb.WatchSet, namespace string) (*structs.VariablesQuota, error) {
   498  	txn := s.db.ReadTxn()
   499  	watchCh, raw, err := txn.FirstWatch(TableVariablesQuotas, indexID, namespace)
   500  	if err != nil {
   501  		return nil, fmt.Errorf("variable quota lookup failed: %v", err)
   502  	}
   503  	ws.Add(watchCh)
   504  
   505  	if raw == nil {
   506  		return nil, nil
   507  	}
   508  	quotaUsed := raw.(*structs.VariablesQuota)
   509  	return quotaUsed, nil
   510  }