github.com/weaviate/weaviate@v1.24.6/adapters/clients/cluster_schema.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 clients
    13  
    14  import (
    15  	"bytes"
    16  	"context"
    17  	"encoding/json"
    18  	"fmt"
    19  	"io"
    20  	"net/http"
    21  	"net/url"
    22  
    23  	"github.com/weaviate/weaviate/usecases/cluster"
    24  )
    25  
    26  type ClusterSchema struct {
    27  	client *http.Client
    28  }
    29  
    30  func NewClusterSchema(httpClient *http.Client) *ClusterSchema {
    31  	return &ClusterSchema{client: httpClient}
    32  }
    33  
    34  func (c *ClusterSchema) OpenTransaction(ctx context.Context, host string,
    35  	tx *cluster.Transaction,
    36  ) error {
    37  	path := "/schema/transactions/"
    38  	method := http.MethodPost
    39  	url := url.URL{Scheme: "http", Host: host, Path: path}
    40  
    41  	pl := txPayload{
    42  		Type:          tx.Type,
    43  		ID:            tx.ID,
    44  		Payload:       tx.Payload,
    45  		DeadlineMilli: tx.Deadline.UnixMilli(),
    46  	}
    47  
    48  	jsonBytes, err := json.Marshal(pl)
    49  	if err != nil {
    50  		return fmt.Errorf("marshal transaction payload: %w", err)
    51  	}
    52  
    53  	req, err := http.NewRequestWithContext(ctx, method, url.String(),
    54  		bytes.NewReader(jsonBytes))
    55  	if err != nil {
    56  		return fmt.Errorf("open http request: %w", err)
    57  	}
    58  
    59  	req.Header.Set("content-type", "application/json")
    60  
    61  	res, err := c.client.Do(req)
    62  	if err != nil {
    63  		return fmt.Errorf("send http request: %w", err)
    64  	}
    65  
    66  	defer res.Body.Close()
    67  	body, _ := io.ReadAll(res.Body)
    68  	if res.StatusCode != http.StatusCreated {
    69  		if res.StatusCode == http.StatusConflict {
    70  			return cluster.ErrConcurrentTransaction
    71  		}
    72  
    73  		return fmt.Errorf("unexpected status code %d (%s)", res.StatusCode,
    74  			body)
    75  	}
    76  
    77  	// optional for backward-compatibility before v1.17 where only
    78  	// write-transactions where supported. They had no return value other than
    79  	// the status code. With the introduction of read-transactions it is now
    80  	// possible to return the requested value
    81  	if len(body) == 0 {
    82  		return nil
    83  	}
    84  
    85  	var txRes txResponsePayload
    86  	err = json.Unmarshal(body, &txRes)
    87  	if err != nil {
    88  		return fmt.Errorf("unexpected error unmarshalling tx response: %w", err)
    89  	}
    90  
    91  	if tx.ID != txRes.ID {
    92  		return fmt.Errorf("unexpected mismatch between outgoing and incoming tx ids:"+
    93  			"%s vs %s", tx.ID, txRes.ID)
    94  	}
    95  
    96  	tx.Payload = txRes.Payload
    97  
    98  	return nil
    99  }
   100  
   101  func (c *ClusterSchema) AbortTransaction(ctx context.Context, host string,
   102  	tx *cluster.Transaction,
   103  ) error {
   104  	path := "/schema/transactions/" + tx.ID
   105  	method := http.MethodDelete
   106  	url := url.URL{Scheme: "http", Host: host, Path: path}
   107  
   108  	req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
   109  	if err != nil {
   110  		return fmt.Errorf("open http request: %w", err)
   111  	}
   112  
   113  	res, err := c.client.Do(req)
   114  	if err != nil {
   115  		return fmt.Errorf("send http request: %w", err)
   116  	}
   117  
   118  	defer res.Body.Close()
   119  	if res.StatusCode != http.StatusNoContent {
   120  		errBody, _ := io.ReadAll(res.Body)
   121  		return fmt.Errorf("unexpected status code %d: %s", res.StatusCode, errBody)
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func (c *ClusterSchema) CommitTransaction(ctx context.Context, host string,
   128  	tx *cluster.Transaction,
   129  ) error {
   130  	path := "/schema/transactions/" + tx.ID + "/commit"
   131  	method := http.MethodPut
   132  	url := url.URL{Scheme: "http", Host: host, Path: path}
   133  
   134  	req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
   135  	if err != nil {
   136  		return fmt.Errorf("open http request: %w", err)
   137  	}
   138  
   139  	res, err := c.client.Do(req)
   140  	if err != nil {
   141  		return fmt.Errorf("send http request: %w", err)
   142  	}
   143  
   144  	defer res.Body.Close()
   145  	if res.StatusCode != http.StatusNoContent {
   146  		errBody, _ := io.ReadAll(res.Body)
   147  		return fmt.Errorf("unexpected status code %d: %s", res.StatusCode, errBody)
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  type txPayload struct {
   154  	Type          cluster.TransactionType `json:"type"`
   155  	ID            string                  `json:"id"`
   156  	Payload       interface{}             `json:"payload"`
   157  	DeadlineMilli int64                   `json:"deadlineMilli"`
   158  }
   159  
   160  type txResponsePayload struct {
   161  	Type    cluster.TransactionType `json:"type"`
   162  	ID      string                  `json:"id"`
   163  	Payload json.RawMessage         `json:"payload"`
   164  }