github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/transactionenv/env.go (about)

     1  package transactionenv
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/gogo/protobuf/proto"
     7  
     8  	"github.com/pachyderm/pachyderm/src/client"
     9  	"github.com/pachyderm/pachyderm/src/client/auth"
    10  	"github.com/pachyderm/pachyderm/src/client/pfs"
    11  	"github.com/pachyderm/pachyderm/src/client/pps"
    12  	"github.com/pachyderm/pachyderm/src/client/transaction"
    13  	auth_iface "github.com/pachyderm/pachyderm/src/server/auth"
    14  	pfs_iface "github.com/pachyderm/pachyderm/src/server/pfs"
    15  	col "github.com/pachyderm/pachyderm/src/server/pkg/collection"
    16  	"github.com/pachyderm/pachyderm/src/server/pkg/serviceenv"
    17  	"github.com/pachyderm/pachyderm/src/server/pkg/transactionenv/txncontext"
    18  	pps_iface "github.com/pachyderm/pachyderm/src/server/pps"
    19  )
    20  
    21  // PfsWrites is an interface providing a wrapper for each operation that
    22  // may be appended to a transaction through PFS.  Each call may either
    23  // directly run the request through PFS or append it to the active transaction,
    24  // depending on if there is an active transaction in the client context.
    25  type PfsWrites interface {
    26  	CreateRepo(*pfs.CreateRepoRequest) error
    27  	DeleteRepo(*pfs.DeleteRepoRequest) error
    28  
    29  	StartCommit(*pfs.StartCommitRequest, *pfs.Commit) (*pfs.Commit, error)
    30  	FinishCommit(*pfs.FinishCommitRequest) error
    31  	DeleteCommit(*pfs.DeleteCommitRequest) error
    32  
    33  	CreateBranch(*pfs.CreateBranchRequest) error
    34  	DeleteBranch(*pfs.DeleteBranchRequest) error
    35  }
    36  
    37  // PpsWrites is an interface providing a wrapper for each operation that
    38  // may be appended to a transaction through PPS.  Each call may either
    39  // directly run the request through PPS or append it to the active transaction,
    40  // depending on if there is an active transaction in the client context.
    41  type PpsWrites interface {
    42  	UpdateJobState(*pps.UpdateJobStateRequest) error
    43  	CreatePipeline(*pps.CreatePipelineRequest, **pfs.Commit) error
    44  }
    45  
    46  // AuthWrites is an interface providing a wrapper for each operation that
    47  // may be appended to a transaction through the Auth server.  Each call may
    48  // either directly run the request through Auth or append it to the active
    49  // transaction, depending on if there is an active transaction in the client
    50  // context.
    51  type AuthWrites interface {
    52  	SetScope(*auth.SetScopeRequest) (*auth.SetScopeResponse, error)
    53  	SetACL(*auth.SetACLRequest) (*auth.SetACLResponse, error)
    54  }
    55  
    56  // TransactionServer is an interface used by other servers to append a request
    57  // to an existing transaction.
    58  type TransactionServer interface {
    59  	AppendRequest(
    60  		context.Context,
    61  		*transaction.Transaction,
    62  		*transaction.TransactionRequest,
    63  	) (*transaction.TransactionResponse, error)
    64  }
    65  
    66  // TransactionEnv contains the APIServer instances for each subsystem that may
    67  // be involved in running transactions so that they can make calls to each other
    68  // without leaving the context of a transaction.  This is a separate object
    69  // because there are cyclic dependencies between APIServer instances.
    70  type TransactionEnv struct {
    71  	serviceEnv *serviceenv.ServiceEnv
    72  	txnServer  TransactionServer
    73  	authServer auth_iface.TransactionServer
    74  	pfsServer  pfs_iface.TransactionServer
    75  	ppsServer  pps_iface.TransactionServer
    76  }
    77  
    78  // Initialize stores the references to APIServer instances in the TransactionEnv
    79  func (env *TransactionEnv) Initialize(
    80  	serviceEnv *serviceenv.ServiceEnv,
    81  	txnServer TransactionServer,
    82  	authServer auth_iface.TransactionServer,
    83  	pfsServer pfs_iface.TransactionServer,
    84  	ppsServer pps_iface.TransactionServer,
    85  ) {
    86  	env.serviceEnv = serviceEnv
    87  	env.txnServer = txnServer
    88  	env.authServer = authServer
    89  	env.pfsServer = pfsServer
    90  	env.ppsServer = ppsServer
    91  }
    92  
    93  // Transaction is an interface to unify the code that may either perform an
    94  // action directly or append an action to an existing transaction (depending on
    95  // if there is an active transaction in the client context metadata).  There
    96  // are two implementations of this interface:
    97  //  directTransaction: all operations will be run directly through the relevant
    98  //    server, all inside the same STM.
    99  //  appendTransaction: all operations will be appended to the active transaction
   100  //    which will then be dryrun so that the response for the operation can be
   101  //    returned.  Each operation that is appended will do a new dryrun, so this
   102  //    isn't as efficient as it could be.
   103  type Transaction interface {
   104  	PfsWrites
   105  	PpsWrites
   106  	AuthWrites
   107  }
   108  
   109  type directTransaction struct {
   110  	txnCtx *txncontext.TransactionContext
   111  	txnEnv *TransactionEnv
   112  }
   113  
   114  // NewDirectTransaction is a helper function to instantiate a directTransaction
   115  // object.  It is exposed so that the transaction API server can run a direct
   116  // transaction even though there is an active transaction in the context (which
   117  // is why it cannot use `WithTransaction`).
   118  func NewDirectTransaction(txnEnv *TransactionEnv, txnCtx *txncontext.TransactionContext) Transaction {
   119  	return &directTransaction{
   120  		txnCtx: txnCtx,
   121  		txnEnv: txnEnv,
   122  	}
   123  }
   124  
   125  func (t *directTransaction) CreateRepo(original *pfs.CreateRepoRequest) error {
   126  	req := proto.Clone(original).(*pfs.CreateRepoRequest)
   127  	return t.txnEnv.pfsServer.CreateRepoInTransaction(t.txnCtx, req)
   128  }
   129  
   130  func (t *directTransaction) DeleteRepo(original *pfs.DeleteRepoRequest) error {
   131  	req := proto.Clone(original).(*pfs.DeleteRepoRequest)
   132  	return t.txnEnv.pfsServer.DeleteRepoInTransaction(t.txnCtx, req)
   133  }
   134  
   135  func (t *directTransaction) StartCommit(original *pfs.StartCommitRequest, commit *pfs.Commit) (*pfs.Commit, error) {
   136  	req := proto.Clone(original).(*pfs.StartCommitRequest)
   137  	return t.txnEnv.pfsServer.StartCommitInTransaction(t.txnCtx, req, commit)
   138  }
   139  
   140  func (t *directTransaction) FinishCommit(original *pfs.FinishCommitRequest) error {
   141  	req := proto.Clone(original).(*pfs.FinishCommitRequest)
   142  	return t.txnEnv.pfsServer.FinishCommitInTransaction(t.txnCtx, req)
   143  }
   144  
   145  func (t *directTransaction) DeleteCommit(original *pfs.DeleteCommitRequest) error {
   146  	req := proto.Clone(original).(*pfs.DeleteCommitRequest)
   147  	return t.txnEnv.pfsServer.DeleteCommitInTransaction(t.txnCtx, req)
   148  }
   149  
   150  func (t *directTransaction) CreateBranch(original *pfs.CreateBranchRequest) error {
   151  	req := proto.Clone(original).(*pfs.CreateBranchRequest)
   152  	return t.txnEnv.pfsServer.CreateBranchInTransaction(t.txnCtx, req)
   153  }
   154  
   155  func (t *directTransaction) DeleteBranch(original *pfs.DeleteBranchRequest) error {
   156  	req := proto.Clone(original).(*pfs.DeleteBranchRequest)
   157  	return t.txnEnv.pfsServer.DeleteBranchInTransaction(t.txnCtx, req)
   158  }
   159  
   160  func (t *directTransaction) UpdateJobState(original *pps.UpdateJobStateRequest) error {
   161  	req := proto.Clone(original).(*pps.UpdateJobStateRequest)
   162  	return t.txnEnv.ppsServer.UpdateJobStateInTransaction(t.txnCtx, req)
   163  }
   164  
   165  func (t *directTransaction) SetScope(original *auth.SetScopeRequest) (*auth.SetScopeResponse, error) {
   166  	req := proto.Clone(original).(*auth.SetScopeRequest)
   167  	return t.txnEnv.authServer.SetScopeInTransaction(t.txnCtx, req)
   168  }
   169  
   170  func (t *directTransaction) SetACL(original *auth.SetACLRequest) (*auth.SetACLResponse, error) {
   171  	req := proto.Clone(original).(*auth.SetACLRequest)
   172  	return t.txnEnv.authServer.SetACLInTransaction(t.txnCtx, req)
   173  }
   174  
   175  func (t *directTransaction) CreatePipeline(original *pps.CreatePipelineRequest, specCommit **pfs.Commit) error {
   176  	req := proto.Clone(original).(*pps.CreatePipelineRequest)
   177  	return t.txnEnv.ppsServer.CreatePipelineInTransaction(t.txnCtx, req, specCommit)
   178  }
   179  
   180  type appendTransaction struct {
   181  	ctx       context.Context
   182  	activeTxn *transaction.Transaction
   183  	txnEnv    *TransactionEnv
   184  }
   185  
   186  func newAppendTransaction(ctx context.Context, activeTxn *transaction.Transaction, txnEnv *TransactionEnv) Transaction {
   187  	return &appendTransaction{
   188  		ctx:       ctx,
   189  		activeTxn: activeTxn,
   190  		txnEnv:    txnEnv,
   191  	}
   192  }
   193  
   194  func (t *appendTransaction) CreateRepo(req *pfs.CreateRepoRequest) error {
   195  	_, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{CreateRepo: req})
   196  	return err
   197  }
   198  
   199  func (t *appendTransaction) DeleteRepo(req *pfs.DeleteRepoRequest) error {
   200  	_, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{DeleteRepo: req})
   201  	return err
   202  }
   203  
   204  func (t *appendTransaction) StartCommit(req *pfs.StartCommitRequest, _ *pfs.Commit) (*pfs.Commit, error) {
   205  	res, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{StartCommit: req})
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  	return res.Commit, nil
   210  }
   211  
   212  func (t *appendTransaction) FinishCommit(req *pfs.FinishCommitRequest) error {
   213  	_, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{FinishCommit: req})
   214  	return err
   215  }
   216  
   217  func (t *appendTransaction) DeleteCommit(req *pfs.DeleteCommitRequest) error {
   218  	_, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{DeleteCommit: req})
   219  	return err
   220  }
   221  
   222  func (t *appendTransaction) CreateBranch(req *pfs.CreateBranchRequest) error {
   223  	_, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{CreateBranch: req})
   224  	return err
   225  }
   226  
   227  func (t *appendTransaction) DeleteBranch(req *pfs.DeleteBranchRequest) error {
   228  	_, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{DeleteBranch: req})
   229  	return err
   230  }
   231  
   232  func (t *appendTransaction) UpdateJobState(req *pps.UpdateJobStateRequest) error {
   233  	_, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{UpdateJobState: req})
   234  	return err
   235  }
   236  
   237  func (t *appendTransaction) CreatePipeline(req *pps.CreatePipelineRequest, _ **pfs.Commit) error {
   238  	_, err := t.txnEnv.txnServer.AppendRequest(t.ctx, t.activeTxn, &transaction.TransactionRequest{CreatePipeline: req})
   239  	return err
   240  }
   241  
   242  func (t *appendTransaction) SetScope(original *auth.SetScopeRequest) (*auth.SetScopeResponse, error) {
   243  	panic("SetScope not yet implemented in transactions")
   244  }
   245  
   246  func (t *appendTransaction) SetACL(original *auth.SetACLRequest) (*auth.SetACLResponse, error) {
   247  	panic("SetACL not yet implemented in transactions")
   248  }
   249  
   250  // WithTransaction will call the given callback with a txnenv.Transaction
   251  // object, which is instantiated differently based on if an active
   252  // transaction is present in the RPC context.  If an active transaction is
   253  // present, any calls into the Transaction are first dry-run then appended
   254  // to the transaction.  If there is no active transaction, the request will be
   255  // run directly through the selected server.
   256  func (env *TransactionEnv) WithTransaction(ctx context.Context, cb func(Transaction) error) error {
   257  	activeTxn, err := client.GetTransaction(ctx)
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	if activeTxn != nil {
   263  		appendTxn := newAppendTransaction(ctx, activeTxn, env)
   264  		return cb(appendTxn)
   265  	}
   266  
   267  	return env.WithWriteContext(ctx, func(txnCtx *txncontext.TransactionContext) error {
   268  		directTxn := NewDirectTransaction(env, txnCtx)
   269  		return cb(directTxn)
   270  	})
   271  }
   272  
   273  // WithWriteContext will call the given callback with a txncontext.TransactionContext
   274  // which can be used to perform reads and writes on the current cluster state.
   275  func (env *TransactionEnv) WithWriteContext(ctx context.Context, cb func(*txncontext.TransactionContext) error) error {
   276  	_, err := col.NewSTM(ctx, env.serviceEnv.GetEtcdClient(), func(stm col.STM) error {
   277  		pachClient := env.serviceEnv.GetPachClient(ctx)
   278  		txnCtx := &txncontext.TransactionContext{
   279  			Client:        pachClient,
   280  			ClientContext: pachClient.Ctx(),
   281  			Stm:           stm,
   282  			PfsPropagater: env.pfsServer.NewPropagater(stm),
   283  		}
   284  		txnCtx.CommitFinisher = env.pfsServer.NewPipelineFinisher(txnCtx)
   285  
   286  		err := cb(txnCtx)
   287  		if err != nil {
   288  			return err
   289  		}
   290  		return txnCtx.Finish()
   291  	})
   292  	return err
   293  }
   294  
   295  // WithReadContext will call the given callback with a txncontext.TransactionContext
   296  // which can be used to perform reads of the current cluster state. If the
   297  // transaction is used to perform any writes, they will be silently discarded.
   298  func (env *TransactionEnv) WithReadContext(ctx context.Context, cb func(*txncontext.TransactionContext) error) error {
   299  	return col.NewDryrunSTM(ctx, env.serviceEnv.GetEtcdClient(), func(stm col.STM) error {
   300  		pachClient := env.serviceEnv.GetPachClient(ctx)
   301  		txnCtx := &txncontext.TransactionContext{
   302  			Client:         pachClient,
   303  			ClientContext:  pachClient.Ctx(),
   304  			Stm:            stm,
   305  			PfsPropagater:  env.pfsServer.NewPropagater(stm),
   306  			CommitFinisher: nil, // don't alter any pipeline commits in a read-only setting
   307  		}
   308  
   309  		err := cb(txnCtx)
   310  		if err != nil {
   311  			return err
   312  		}
   313  		return txnCtx.Finish()
   314  	})
   315  }