add transactions external documentation

- make sure client reads/closes all bodies
- support blank transaction ID in http handler
This commit is contained in:
Matt Jaffee 2020-04-18 08:29:26 -05:00
parent 210c7239ab
commit 41975de6b8
No known key found for this signature in database
GPG key ID: 08A3DFFF987B11BF
4 changed files with 106 additions and 6 deletions

View file

@ -1247,7 +1247,10 @@ func (c *InternalClient) Transactions(ctx context.Context) (map[string]pilosa.Tr
if err != nil {
return trnsMap, errors.Wrap(err, "executing request")
}
defer resp.Body.Close()
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()
tmpTrnsMap := make(map[string]*pilosa.Transaction)
err = json.NewDecoder(resp.Body).Decode(&tmpTrnsMap)
@ -1288,7 +1291,10 @@ func (c *InternalClient) StartTransaction(ctx context.Context, id string, timeou
if err != nil {
return pilosa.Transaction{}, errors.Wrap(err, "executing request")
}
defer resp.Body.Close()
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()
err = json.NewDecoder(resp.Body).Decode(&tr)
if err != nil {
return pilosa.Transaction{}, errors.Wrap(err, "decoding response")
@ -1318,7 +1324,10 @@ func (c *InternalClient) FinishTransaction(ctx context.Context, id string) (pilo
if err != nil {
return pilosa.Transaction{}, errors.Wrap(err, "executing request")
}
defer resp.Body.Close()
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()
tr := &TransactionResponse{Transaction: &pilosa.Transaction{}}
err = json.NewDecoder(resp.Body).Decode(&tr)
if err != nil {
@ -1351,7 +1360,10 @@ func (c *InternalClient) GetTransaction(ctx context.Context, id string) (pilosa.
if err != nil {
return pilosa.Transaction{}, errors.Wrap(err, "executing request")
}
defer resp.Body.Close()
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()
tr := &TransactionResponse{Transaction: &pilosa.Transaction{}}
err = json.NewDecoder(resp.Body).Decode(&tr)
if err != nil {

View file

@ -1386,6 +1386,20 @@ func TestClientTransactions(t *testing.T) {
pilosa.Transaction{},
trns)
}
// start transaction with blank id
if trns, err := client0.StartTransaction(context.Background(), "", time.Minute, false); err != nil {
t.Fatalf("error starting transaction: %v", err)
} else {
expDeadline = time.Now().Add(time.Minute)
if len(trns.ID) != 36 {
t.Errorf("expected generated UUID, but got '%s'", trns.ID)
}
test.CompareTransactions(t,
pilosa.Transaction{ID: trns.ID, Timeout: time.Minute, Active: true, Deadline: expDeadline},
trns)
}
}
// Client represents a test wrapper for pilosa.Client.

View file

@ -343,6 +343,8 @@ func newRouter(handler *Handler) *mux.Router {
router.HandleFunc("/version", handler.handleGetVersion).Methods("GET").Name("GetVersion")
router.HandleFunc("/transactions", handler.handleGetTransactions).Methods("GET").Name("GetTransactions")
router.HandleFunc("/transaction/{id}", handler.handleGetTransaction).Methods("GET").Name("GetTransaction")
router.HandleFunc("/transaction/", handler.handlePostTransaction).Methods("POST").Name("PostTransaction")
router.HandleFunc("/transaction", handler.handlePostTransaction).Methods("POST").Name("PostTransaction")
router.HandleFunc("/transaction/{id}", handler.handlePostTransaction).Methods("POST").Name("PostTransaction")
router.HandleFunc("/transaction/{id}/finish", handler.handlePostFinishTransaction).Methods("POST").Name("PostFinishTransaction")
@ -1124,7 +1126,10 @@ func (h *Handler) handlePostTransaction(w http.ResponseWriter, r *http.Request)
return
}
id := mux.Vars(r)["id"]
id, ok := mux.Vars(r)["id"]
if !ok {
id = reqTrns.ID
}
trns, err := h.api.StartTransaction(id, reqTrns.Timeout, reqTrns.Exclusive, false)
h.doTransactionResponse(w, err, trns)

View file

@ -159,11 +159,13 @@ goes through API (and is passed directly to Server). (unimplemented)
#### TODO
- [x] implement api layer and cluster logic, startup, etc.
- [x] implement api layer and cluster logic, startup, etc.
- [ ] add new cluster state to explicitly reject certain requests during exclusive transaction?
- [ ] implement HTTP layer including header/transaction ID
- [ ] implement and use persistent transaction store rather than inmem.
- [ ] update go-pilosa/gpexp to actually USE transactions
- [ ] update IDK to use updated go-pilosa
- [ ] external testing with e.g. curl
- ID validation. No slashes, no non-URL safe chars
@ -172,3 +174,70 @@ goes through API (and is passed directly to Server). (unimplemented)
- if the Exclusive transaction is active, there should be no other transactions
### Documentation
Before performing a backup, you must request an exclusive "transaction" with the cluster. Do this via and HTTP POST to the coordinator node at path:
`/transaction` OR `/transaction/{id}` if you wish to specify a custom ID (any alphanum+dash). Otherwise a UUID will be generated and returned in the response.
Use headers:
```
Accept: application/json
Content-Type: application/json
```
And body like:
```
{
"timeout": "10m",
"exclusive": true
}
```
You may choose any timeout you like, though it's better to err on the
longer side of how long you expect the backup to take. You explicitly
finish the transaction once you're done, so the timeout exists solely
for cleanup in the case of failures.
This will return a JSON "transaction response" object.
```
{
"transaction": {
"id":"5e572d95-4204-40cd-804c-92976b68dc9b",
"active":true,
"exclusive":false,
"timeout":"1m0s",
"deadline":"2020-04-17T21:54:18.69359-05:00"
},
"error":"some message"
}
```
The `error` field MAY not be present if there is no error.
You MUST check whether `active` is true. If not, you must poll the transaction endpoint with a GET request and your ID until it is true. This looks like:
GET `/transaction/5e572d95-4204-40cd-804c-92976b68dc9b`
with headers:
```
Accept: application/json
```
and also returns a "transaction response" object.
Once an "active", "exclusive" transaction is returned, proceed with your backup.
Once the backup is complete, finish the transaction with
POST `/transaction/{id}/finish`
with headers:
```
Accept: application/json
```