github.com/outbrain/consul@v1.4.5/api/txn.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  )
     9  
    10  // Txn is used to manipulate the Txn API
    11  type Txn struct {
    12  	c *Client
    13  }
    14  
    15  // Txn is used to return a handle to the K/V apis
    16  func (c *Client) Txn() *Txn {
    17  	return &Txn{c}
    18  }
    19  
    20  // TxnOp is the internal format we send to Consul. Currently only K/V and
    21  // check operations are supported.
    22  type TxnOp struct {
    23  	KV      *KVTxnOp
    24  	Node    *NodeTxnOp
    25  	Service *ServiceTxnOp
    26  	Check   *CheckTxnOp
    27  }
    28  
    29  // TxnOps is a list of transaction operations.
    30  type TxnOps []*TxnOp
    31  
    32  // TxnResult is the internal format we receive from Consul.
    33  type TxnResult struct {
    34  	KV      *KVPair
    35  	Node    *Node
    36  	Service *CatalogService
    37  	Check   *HealthCheck
    38  }
    39  
    40  // TxnResults is a list of TxnResult objects.
    41  type TxnResults []*TxnResult
    42  
    43  // TxnError is used to return information about an operation in a transaction.
    44  type TxnError struct {
    45  	OpIndex int
    46  	What    string
    47  }
    48  
    49  // TxnErrors is a list of TxnError objects.
    50  type TxnErrors []*TxnError
    51  
    52  // TxnResponse is the internal format we receive from Consul.
    53  type TxnResponse struct {
    54  	Results TxnResults
    55  	Errors  TxnErrors
    56  }
    57  
    58  // KVOp constants give possible operations available in a transaction.
    59  type KVOp string
    60  
    61  const (
    62  	KVSet            KVOp = "set"
    63  	KVDelete         KVOp = "delete"
    64  	KVDeleteCAS      KVOp = "delete-cas"
    65  	KVDeleteTree     KVOp = "delete-tree"
    66  	KVCAS            KVOp = "cas"
    67  	KVLock           KVOp = "lock"
    68  	KVUnlock         KVOp = "unlock"
    69  	KVGet            KVOp = "get"
    70  	KVGetTree        KVOp = "get-tree"
    71  	KVCheckSession   KVOp = "check-session"
    72  	KVCheckIndex     KVOp = "check-index"
    73  	KVCheckNotExists KVOp = "check-not-exists"
    74  )
    75  
    76  // KVTxnOp defines a single operation inside a transaction.
    77  type KVTxnOp struct {
    78  	Verb    KVOp
    79  	Key     string
    80  	Value   []byte
    81  	Flags   uint64
    82  	Index   uint64
    83  	Session string
    84  }
    85  
    86  // KVTxnOps defines a set of operations to be performed inside a single
    87  // transaction.
    88  type KVTxnOps []*KVTxnOp
    89  
    90  // KVTxnResponse has the outcome of a transaction.
    91  type KVTxnResponse struct {
    92  	Results []*KVPair
    93  	Errors  TxnErrors
    94  }
    95  
    96  // NodeOp constants give possible operations available in a transaction.
    97  type NodeOp string
    98  
    99  const (
   100  	NodeGet       NodeOp = "get"
   101  	NodeSet       NodeOp = "set"
   102  	NodeCAS       NodeOp = "cas"
   103  	NodeDelete    NodeOp = "delete"
   104  	NodeDeleteCAS NodeOp = "delete-cas"
   105  )
   106  
   107  // NodeTxnOp defines a single operation inside a transaction.
   108  type NodeTxnOp struct {
   109  	Verb NodeOp
   110  	Node Node
   111  }
   112  
   113  // ServiceOp constants give possible operations available in a transaction.
   114  type ServiceOp string
   115  
   116  const (
   117  	ServiceGet       ServiceOp = "get"
   118  	ServiceSet       ServiceOp = "set"
   119  	ServiceCAS       ServiceOp = "cas"
   120  	ServiceDelete    ServiceOp = "delete"
   121  	ServiceDeleteCAS ServiceOp = "delete-cas"
   122  )
   123  
   124  // ServiceTxnOp defines a single operation inside a transaction.
   125  type ServiceTxnOp struct {
   126  	Verb    ServiceOp
   127  	Node    string
   128  	Service AgentService
   129  }
   130  
   131  // CheckOp constants give possible operations available in a transaction.
   132  type CheckOp string
   133  
   134  const (
   135  	CheckGet       CheckOp = "get"
   136  	CheckSet       CheckOp = "set"
   137  	CheckCAS       CheckOp = "cas"
   138  	CheckDelete    CheckOp = "delete"
   139  	CheckDeleteCAS CheckOp = "delete-cas"
   140  )
   141  
   142  // CheckTxnOp defines a single operation inside a transaction.
   143  type CheckTxnOp struct {
   144  	Verb  CheckOp
   145  	Check HealthCheck
   146  }
   147  
   148  // Txn is used to apply multiple Consul operations in a single, atomic transaction.
   149  //
   150  // Note that Go will perform the required base64 encoding on the values
   151  // automatically because the type is a byte slice. Transactions are defined as a
   152  // list of operations to perform, using the different fields in the TxnOp structure
   153  // to define operations. If any operation fails, none of the changes are applied
   154  // to the state store.
   155  //
   156  // Even though this is generally a write operation, we take a QueryOptions input
   157  // and return a QueryMeta output. If the transaction contains only read ops, then
   158  // Consul will fast-path it to a different endpoint internally which supports
   159  // consistency controls, but not blocking. If there are write operations then
   160  // the request will always be routed through raft and any consistency settings
   161  // will be ignored.
   162  //
   163  // Here's an example:
   164  //
   165  //	   ops := KVTxnOps{
   166  //		   &KVTxnOp{
   167  //			   Verb:    KVLock,
   168  //			   Key:     "test/lock",
   169  //			   Session: "adf4238a-882b-9ddc-4a9d-5b6758e4159e",
   170  //			   Value:   []byte("hello"),
   171  //		   },
   172  //		   &KVTxnOp{
   173  //			   Verb:    KVGet,
   174  //			   Key:     "another/key",
   175  //		   },
   176  //		   &CheckTxnOp{
   177  //			   Verb:        CheckSet,
   178  //			   HealthCheck: HealthCheck{
   179  //				   Node:    "foo",
   180  //				   CheckID: "redis:a",
   181  //				   Name:    "Redis Health Check",
   182  //				   Status:  "passing",
   183  //			   },
   184  //		   }
   185  //	   }
   186  //	   ok, response, _, err := kv.Txn(&ops, nil)
   187  //
   188  // If there is a problem making the transaction request then an error will be
   189  // returned. Otherwise, the ok value will be true if the transaction succeeded
   190  // or false if it was rolled back. The response is a structured return value which
   191  // will have the outcome of the transaction. Its Results member will have entries
   192  // for each operation. For KV operations, Deleted keys will have a nil entry in the
   193  // results, and to save space, the Value of each key in the Results will be nil
   194  // unless the operation is a KVGet. If the transaction was rolled back, the Errors
   195  // member will have entries referencing the index of the operation that failed
   196  // along with an error message.
   197  func (t *Txn) Txn(txn TxnOps, q *QueryOptions) (bool, *TxnResponse, *QueryMeta, error) {
   198  	return t.c.txn(txn, q)
   199  }
   200  
   201  func (c *Client) txn(txn TxnOps, q *QueryOptions) (bool, *TxnResponse, *QueryMeta, error) {
   202  	r := c.newRequest("PUT", "/v1/txn")
   203  	r.setQueryOptions(q)
   204  
   205  	r.obj = txn
   206  	rtt, resp, err := c.doRequest(r)
   207  	if err != nil {
   208  		return false, nil, nil, err
   209  	}
   210  	defer resp.Body.Close()
   211  
   212  	qm := &QueryMeta{}
   213  	parseQueryMeta(resp, qm)
   214  	qm.RequestTime = rtt
   215  
   216  	if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusConflict {
   217  		var txnResp TxnResponse
   218  		if err := decodeBody(resp, &txnResp); err != nil {
   219  			return false, nil, nil, err
   220  		}
   221  
   222  		return resp.StatusCode == http.StatusOK, &txnResp, qm, nil
   223  	}
   224  
   225  	var buf bytes.Buffer
   226  	if _, err := io.Copy(&buf, resp.Body); err != nil {
   227  		return false, nil, nil, fmt.Errorf("Failed to read response: %v", err)
   228  	}
   229  	return false, nil, nil, fmt.Errorf("Failed request: %s", buf.String())
   230  }