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  }