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 }