github.com/weaviate/weaviate@v1.24.6/adapters/handlers/rest/clusterapi/transactions.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package clusterapi 13 14 import ( 15 "context" 16 "encoding/json" 17 "net/http" 18 "strings" 19 "time" 20 21 "github.com/pkg/errors" 22 "github.com/weaviate/weaviate/usecases/cluster" 23 ucs "github.com/weaviate/weaviate/usecases/schema" 24 ) 25 26 type txManager interface { 27 IncomingBeginTransaction(ctx context.Context, tx *cluster.Transaction) ([]byte, error) 28 IncomingCommitTransaction(ctx context.Context, tx *cluster.Transaction) error 29 IncomingAbortTransaction(ctx context.Context, tx *cluster.Transaction) 30 } 31 32 type txPayload struct { 33 ID string `json:"id"` 34 Type cluster.TransactionType `json:"type"` 35 Payload json.RawMessage `json:"payload"` 36 DeadlineMilli int64 `json:"deadlineMilli"` 37 } 38 39 type txHandler struct { 40 manager txManager 41 auth auth 42 } 43 44 func (h *txHandler) Transactions() http.Handler { 45 return h.auth.handleFunc(h.transactionsHandler()) 46 } 47 48 func (h *txHandler) transactionsHandler() http.HandlerFunc { 49 return func(w http.ResponseWriter, r *http.Request) { 50 path := r.URL.Path 51 switch { 52 case path == "": 53 if r.Method != http.MethodPost { 54 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 55 return 56 } 57 58 h.incomingTransaction().ServeHTTP(w, r) 59 return 60 61 case strings.HasSuffix(path, "/commit"): 62 if r.Method != http.MethodPut { 63 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 64 return 65 } 66 67 h.incomingCommitTransaction().ServeHTTP(w, r) 68 return 69 default: 70 if r.Method != http.MethodDelete { 71 http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed) 72 return 73 } 74 75 h.incomingAbortTransaction().ServeHTTP(w, r) 76 return 77 } 78 } 79 } 80 81 func (h *txHandler) incomingTransaction() http.Handler { 82 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 83 defer r.Body.Close() 84 85 if r.Header.Get("content-type") != "application/json" { 86 http.Error(w, "415 Unsupported Media Type", http.StatusUnsupportedMediaType) 87 return 88 } 89 90 var payload txPayload 91 if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { 92 http.Error(w, errors.Wrap(err, "decode body").Error(), 93 http.StatusInternalServerError) 94 return 95 } 96 97 if len(payload.ID) == 0 { 98 http.Error(w, "id must be set", http.StatusBadRequest) 99 return 100 } 101 102 if len(payload.Type) == 0 { 103 http.Error(w, "type must be set", http.StatusBadRequest) 104 return 105 } 106 107 txPayload, err := ucs.UnmarshalTransaction(payload.Type, payload.Payload) 108 if err != nil { 109 http.Error(w, errors.Wrap(err, "decode tx payload").Error(), 110 http.StatusInternalServerError) 111 return 112 } 113 txType := payload.Type 114 tx := &cluster.Transaction{ 115 ID: payload.ID, 116 Type: txType, 117 Payload: txPayload, 118 Deadline: time.UnixMilli(payload.DeadlineMilli), 119 } 120 121 data, err := h.manager.IncomingBeginTransaction(r.Context(), tx) 122 if err != nil { 123 status := http.StatusInternalServerError 124 if errors.Is(err, cluster.ErrConcurrentTransaction) { 125 status = http.StatusConflict 126 } 127 128 http.Error(w, errors.Wrap(err, "open transaction").Error(), status) 129 return 130 } 131 if txType != ucs.ReadSchema { 132 w.WriteHeader(http.StatusCreated) 133 return 134 } 135 136 if err != nil { 137 w.WriteHeader(http.StatusInternalServerError) 138 w.Write([]byte(err.Error())) 139 } 140 w.WriteHeader(http.StatusCreated) 141 w.Write(data) 142 }) 143 } 144 145 func (h *txHandler) incomingAbortTransaction() http.Handler { 146 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 147 defer r.Body.Close() 148 149 path := r.URL.String() 150 tx := &cluster.Transaction{ 151 ID: path, 152 } 153 154 h.manager.IncomingAbortTransaction(r.Context(), tx) 155 w.WriteHeader(http.StatusNoContent) 156 }) 157 } 158 159 func (h *txHandler) incomingCommitTransaction() http.Handler { 160 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 161 defer r.Body.Close() 162 163 parts := strings.Split(r.URL.Path, "/") 164 if len(parts) != 2 { 165 http.NotFound(w, r) 166 return 167 } 168 169 tx := &cluster.Transaction{ 170 ID: parts[0], 171 } 172 173 if err := h.manager.IncomingCommitTransaction(r.Context(), tx); err != nil { 174 status := http.StatusInternalServerError 175 if errors.Is(err, cluster.ErrConcurrentTransaction) { 176 status = http.StatusConflict 177 } 178 179 http.Error(w, errors.Wrap(err, "open transaction").Error(), status) 180 return 181 } 182 w.WriteHeader(http.StatusNoContent) 183 }) 184 }