github.com/KyaXTeam/consul@v1.4.5/agent/consul/txn_endpoint.go (about)

     1  package consul
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/armon/go-metrics"
     8  	"github.com/hashicorp/consul/acl"
     9  	"github.com/hashicorp/consul/agent/structs"
    10  	"github.com/hashicorp/consul/api"
    11  )
    12  
    13  // Txn endpoint is used to perform multi-object atomic transactions.
    14  type Txn struct {
    15  	srv *Server
    16  }
    17  
    18  // preCheck is used to verify the incoming operations before any further
    19  // processing takes place. This checks things like ACLs.
    20  func (t *Txn) preCheck(authorizer acl.Authorizer, ops structs.TxnOps) structs.TxnErrors {
    21  	var errors structs.TxnErrors
    22  
    23  	// Perform the pre-apply checks for any KV operations.
    24  	for i, op := range ops {
    25  		switch {
    26  		case op.KV != nil:
    27  			ok, err := kvsPreApply(t.srv, authorizer, op.KV.Verb, &op.KV.DirEnt)
    28  			if err != nil {
    29  				errors = append(errors, &structs.TxnError{
    30  					OpIndex: i,
    31  					What:    err.Error(),
    32  				})
    33  			} else if !ok {
    34  				err = fmt.Errorf("failed to lock key %q due to lock delay", op.KV.DirEnt.Key)
    35  				errors = append(errors, &structs.TxnError{
    36  					OpIndex: i,
    37  					What:    err.Error(),
    38  				})
    39  			}
    40  		case op.Node != nil:
    41  			// Skip the pre-apply checks if this is a GET.
    42  			if op.Node.Verb == api.NodeGet {
    43  				break
    44  			}
    45  
    46  			node := op.Node.Node
    47  			if err := nodePreApply(node.Node, string(node.ID)); err != nil {
    48  				errors = append(errors, &structs.TxnError{
    49  					OpIndex: i,
    50  					What:    err.Error(),
    51  				})
    52  				break
    53  			}
    54  
    55  			// Check that the token has permissions for the given operation.
    56  			if err := vetNodeTxnOp(op.Node, authorizer); err != nil {
    57  				errors = append(errors, &structs.TxnError{
    58  					OpIndex: i,
    59  					What:    err.Error(),
    60  				})
    61  			}
    62  		case op.Service != nil:
    63  			// Skip the pre-apply checks if this is a GET.
    64  			if op.Service.Verb == api.ServiceGet {
    65  				break
    66  			}
    67  
    68  			service := &op.Service.Service
    69  			if err := servicePreApply(service, nil); err != nil {
    70  				errors = append(errors, &structs.TxnError{
    71  					OpIndex: i,
    72  					What:    err.Error(),
    73  				})
    74  				break
    75  			}
    76  
    77  			// Check that the token has permissions for the given operation.
    78  			if err := vetServiceTxnOp(op.Service, authorizer); err != nil {
    79  				errors = append(errors, &structs.TxnError{
    80  					OpIndex: i,
    81  					What:    err.Error(),
    82  				})
    83  			}
    84  		case op.Check != nil:
    85  			// Skip the pre-apply checks if this is a GET.
    86  			if op.Check.Verb == api.CheckGet {
    87  				break
    88  			}
    89  
    90  			checkPreApply(&op.Check.Check)
    91  
    92  			// Check that the token has permissions for the given operation.
    93  			if err := vetCheckTxnOp(op.Check, authorizer); err != nil {
    94  				errors = append(errors, &structs.TxnError{
    95  					OpIndex: i,
    96  					What:    err.Error(),
    97  				})
    98  			}
    99  		}
   100  	}
   101  
   102  	return errors
   103  }
   104  
   105  // Apply is used to apply multiple operations in a single, atomic transaction.
   106  func (t *Txn) Apply(args *structs.TxnRequest, reply *structs.TxnResponse) error {
   107  	if done, err := t.srv.forward("Txn.Apply", args, args, reply); done {
   108  		return err
   109  	}
   110  	defer metrics.MeasureSince([]string{"txn", "apply"}, time.Now())
   111  
   112  	// Run the pre-checks before we send the transaction into Raft.
   113  	authorizer, err := t.srv.ResolveToken(args.Token)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	reply.Errors = t.preCheck(authorizer, args.Ops)
   118  	if len(reply.Errors) > 0 {
   119  		return nil
   120  	}
   121  
   122  	// Apply the update.
   123  	resp, err := t.srv.raftApply(structs.TxnRequestType, args)
   124  	if err != nil {
   125  		t.srv.logger.Printf("[ERR] consul.txn: Apply failed: %v", err)
   126  		return err
   127  	}
   128  	if respErr, ok := resp.(error); ok {
   129  		return respErr
   130  	}
   131  
   132  	// Convert the return type. This should be a cheap copy since we are
   133  	// just taking the two slices.
   134  	if txnResp, ok := resp.(structs.TxnResponse); ok {
   135  		if authorizer != nil {
   136  			txnResp.Results = FilterTxnResults(authorizer, txnResp.Results)
   137  		}
   138  		*reply = txnResp
   139  	} else {
   140  		return fmt.Errorf("unexpected return type %T", resp)
   141  	}
   142  	return nil
   143  }
   144  
   145  // Read is used to perform a read-only transaction that doesn't modify the state
   146  // store. This is much more scalable since it doesn't go through Raft and
   147  // supports staleness, so this should be preferred if you're just performing
   148  // reads.
   149  func (t *Txn) Read(args *structs.TxnReadRequest, reply *structs.TxnReadResponse) error {
   150  	if done, err := t.srv.forward("Txn.Read", args, args, reply); done {
   151  		return err
   152  	}
   153  	defer metrics.MeasureSince([]string{"txn", "read"}, time.Now())
   154  
   155  	// We have to do this ourselves since we are not doing a blocking RPC.
   156  	t.srv.setQueryMeta(&reply.QueryMeta)
   157  	if args.RequireConsistent {
   158  		if err := t.srv.consistentRead(); err != nil {
   159  			return err
   160  		}
   161  	}
   162  
   163  	// Run the pre-checks before we perform the read.
   164  	authorizer, err := t.srv.ResolveToken(args.Token)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	reply.Errors = t.preCheck(authorizer, args.Ops)
   169  	if len(reply.Errors) > 0 {
   170  		return nil
   171  	}
   172  
   173  	// Run the read transaction.
   174  	state := t.srv.fsm.State()
   175  	reply.Results, reply.Errors = state.TxnRO(args.Ops)
   176  	if authorizer != nil {
   177  		reply.Results = FilterTxnResults(authorizer, reply.Results)
   178  	}
   179  	return nil
   180  }