blob: 65d7a16ea040564badf8978cb35b0935d484db4e [file] [log] [blame]
William Kurkianea869482019-04-09 15:16:11 -04001package api
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "net/http"
8)
9
10// Txn is used to manipulate the Txn API
11type Txn struct {
12 c *Client
13}
14
15// Txn is used to return a handle to the K/V apis
16func (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.
22type TxnOp struct {
23 KV *KVTxnOp
24 Node *NodeTxnOp
25 Service *ServiceTxnOp
26 Check *CheckTxnOp
27}
28
29// TxnOps is a list of transaction operations.
30type TxnOps []*TxnOp
31
32// TxnResult is the internal format we receive from Consul.
33type TxnResult struct {
34 KV *KVPair
35 Node *Node
36 Service *CatalogService
37 Check *HealthCheck
38}
39
40// TxnResults is a list of TxnResult objects.
41type TxnResults []*TxnResult
42
43// TxnError is used to return information about an operation in a transaction.
44type TxnError struct {
45 OpIndex int
46 What string
47}
48
49// TxnErrors is a list of TxnError objects.
50type TxnErrors []*TxnError
51
52// TxnResponse is the internal format we receive from Consul.
53type TxnResponse struct {
54 Results TxnResults
55 Errors TxnErrors
56}
57
58// KVOp constants give possible operations available in a transaction.
59type KVOp string
60
61const (
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.
77type 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.
88type KVTxnOps []*KVTxnOp
89
90// KVTxnResponse has the outcome of a transaction.
91type KVTxnResponse struct {
92 Results []*KVPair
93 Errors TxnErrors
94}
95
96// NodeOp constants give possible operations available in a transaction.
97type NodeOp string
98
99const (
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.
108type NodeTxnOp struct {
109 Verb NodeOp
110 Node Node
111}
112
113// ServiceOp constants give possible operations available in a transaction.
114type ServiceOp string
115
116const (
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.
125type ServiceTxnOp struct {
126 Verb ServiceOp
127 Node string
128 Service AgentService
129}
130
131// CheckOp constants give possible operations available in a transaction.
132type CheckOp string
133
134const (
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.
143type 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.
197func (t *Txn) Txn(txn TxnOps, q *QueryOptions) (bool, *TxnResponse, *QueryMeta, error) {
198 return t.c.txn(txn, q)
199}
200
201func (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}