github.com/sequix/cortex@v1.1.6/pkg/chunk/ts/client.go (about)

     1  package ts
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/url"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/baidubce/bce-sdk-go/auth"
    15  	"github.com/baidubce/bce-sdk-go/http"
    16  	"github.com/go-kit/kit/log/level"
    17  
    18  	"github.com/sequix/cortex/pkg/chunk"
    19  	"github.com/sequix/cortex/pkg/util"
    20  )
    21  
    22  var (
    23  	maxChunks = 5
    24  	signer    = &auth.BceV1Signer{}
    25  )
    26  
    27  type Config struct {
    28  	Endpoint  string `yaml:"endpoint"`
    29  	AccessKey string `yaml:"ak"`
    30  	SecretKey string `yaml:"sk"`
    31  	Instance  string `yaml:"instance"`
    32  	Table     string `yaml:"table"`
    33  }
    34  
    35  type client struct {
    36  	credential    *auth.BceCredentials
    37  	endpoint      string
    38  	uri           string
    39  	headersToSign map[string]struct{}
    40  }
    41  
    42  func New(c Config) (*client, error) {
    43  	cred, err := auth.NewBceCredentials(c.AccessKey, c.SecretKey)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	client := &client{
    49  		credential: cred,
    50  		endpoint:   c.Endpoint,
    51  		uri:        "/v1/instance/" + c.Instance + "/table/" + c.Table + "/rows",
    52  		headersToSign: map[string]struct{}{
    53  			"host":       struct{}{},
    54  			"x-bce-date": struct{}{},
    55  		},
    56  	}
    57  	return client, nil
    58  }
    59  
    60  func (c *client) Stop() {
    61  	// 为了实现 type chunk.ObjectClient interface
    62  }
    63  
    64  func splitReq(chunks []chunk.Chunk, max int, f func([]chunk.Chunk) error) error {
    65  	n := len(chunks)
    66  	for i := 0; i < n; i += maxChunks {
    67  		j := i + max
    68  		if j > n {
    69  			j = n
    70  		}
    71  		if err := f(chunks[i:j]); err != nil {
    72  			return err
    73  		}
    74  	}
    75  	return nil
    76  }
    77  
    78  func (c *client) PutChunks(ctx context.Context, chunks []chunk.Chunk) error {
    79  	return splitReq(chunks, maxChunks, c.put)
    80  }
    81  
    82  func (c *client) GetChunks(ctx context.Context, chunks []chunk.Chunk) ([]chunk.Chunk, error) {
    83  	if err := splitReq(chunks, maxChunks, c.get); err != nil {
    84  		return nil, err
    85  	}
    86  	return chunks, nil
    87  }
    88  
    89  func (c *client) newRequest(now time.Time) *http.Request {
    90  	req := &http.Request{}
    91  	req.SetProtocol("https")
    92  	req.SetEndpoint(c.endpoint)
    93  	req.SetHeader("host", c.endpoint)
    94  	req.SetHeader("x-bce-date", now.Format(time.RFC3339))
    95  	req.SetHeader("Content-Type", "application/json")
    96  	//req.SetHeader("Accept", "application/json")
    97  	req.SetUri(c.uri)
    98  	return req
    99  }
   100  
   101  func (c *client) put(chunks []chunk.Chunk) error {
   102  	reqBody := &batchPutRowReq{
   103  		Rows: make([]*row, 0, len(chunks)),
   104  	}
   105  
   106  	for i := range chunks {
   107  		r, err := chunkToRow(&chunks[i])
   108  		if err != nil {
   109  			return err
   110  		}
   111  		reqBody.Rows = append(reqBody.Rows, r)
   112  	}
   113  
   114  	reqBodyBuf, err := json.Marshal(reqBody)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	now := time.Now().UTC()
   120  	req := c.newRequest(now)
   121  	req.SetMethod(http.PUT)
   122  	req.SetLength(int64(len(reqBodyBuf)))
   123  	req.SetBody(ioutil.NopCloser(bytes.NewReader(reqBodyBuf)))
   124  
   125  	base := time.Now()
   126  	_, err = c.do(now, req)
   127  	level.Debug(util.Logger).Log("module", "TableStorageClient", "msg", "Put Rows", "size", len(reqBodyBuf), "duration", time.Since(base), "err", err)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	return nil
   132  }
   133  
   134  func (c *client) get(chunks []chunk.Chunk) error {
   135  	reqBody := &batchGetRowReq{
   136  		Rows: make([]*row, 0, len(chunks)),
   137  	}
   138  
   139  	for i := range chunks {
   140  		reqBody.Rows = append(reqBody.Rows, chunkToRowWithoutContent(&chunks[i]))
   141  	}
   142  
   143  	reqBodyBuf, err := json.Marshal(reqBody)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	now := time.Now().UTC()
   149  	req := c.newRequest(now)
   150  	req.SetMethod(http.GET)
   151  	req.SetLength(int64(len(reqBodyBuf)))
   152  	req.SetBody(ioutil.NopCloser(bytes.NewReader(reqBodyBuf)))
   153  
   154  	base := time.Now()
   155  	rsp, err := c.do(now, req)
   156  	level.Debug(util.Logger).Log("module", "TableStorageClient", "msg", "Get Rows", "size", len(reqBodyBuf), "duration", time.Since(base), "err", err)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	defer rsp.Body().Close()
   161  
   162  	rspBodyBytes, err := ioutil.ReadAll(rsp.Body())
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	rspBody := &batchGetRowRsp{}
   168  	err = json.Unmarshal(rspBodyBytes, rspBody)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	rows := rspBody.Rows
   173  
   174  	keyToRow := make(map[string]*row)
   175  	for i := range rows {
   176  		keyToRow[rawUrlDecode(rows[i].RowKey)] = rows[i]
   177  	}
   178  
   179  	for i := range chunks {
   180  		r, ok := keyToRow[chunks[i].ExternalKey()]
   181  		if !ok {
   182  			continue
   183  		}
   184  		err := rowToChunk(r, &chunks[i])
   185  		if err != nil {
   186  			return err
   187  		}
   188  	}
   189  	return nil
   190  }
   191  
   192  func (c *client) do(now time.Time, req *http.Request) (*http.Response, error) {
   193  	signer.Sign(req, c.credential, &auth.SignOptions{
   194  		HeadersToSign: c.headersToSign,
   195  		Timestamp:     now.Unix(),
   196  		ExpireSeconds: 180,
   197  	})
   198  	rsp, origErr := http.Execute(req)
   199  	if origErr != nil {
   200  		return rsp, nil
   201  	}
   202  
   203  	if rsp.StatusCode() > 399 {
   204  		content, err := ioutil.ReadAll(io.LimitReader(rsp.Body(), 256))
   205  		if err != nil {
   206  			return nil, fmt.Errorf("ts client reading response body failed")
   207  		}
   208  		return nil, fmt.Errorf("ts client %s rows: %s", req.Method(), content)
   209  	}
   210  	return rsp, nil
   211  }
   212  
   213  func rawUrlEncode(s string) string {
   214  	es := url.QueryEscape(s)
   215  	return strings.ReplaceAll(es, "+", "%20")
   216  }
   217  
   218  func rawUrlDecode(s string) string {
   219  	rs := strings.ReplaceAll(s, "%20", "+")
   220  	ds, _ := url.QueryUnescape(rs)
   221  	return ds
   222  }
   223  
   224  func chunkToRow(chunk *chunk.Chunk) (*row, error) {
   225  	r := &row{
   226  		RowKey: rawUrlEncode(chunk.ExternalKey()),
   227  		Cells: []*cell{
   228  			{
   229  				Column: columnName,
   230  			},
   231  		},
   232  	}
   233  	buf, err := chunk.Encoded()
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  	r.Cells[0].Value = rawUrlEncode(string(buf))
   238  	return r, nil
   239  }
   240  
   241  func chunkToRowWithoutContent(chunk *chunk.Chunk) *row {
   242  	return &row{
   243  		RowKey: rawUrlEncode(chunk.ExternalKey()),
   244  		Cells: []*cell{
   245  			{
   246  				Column: columnName,
   247  			},
   248  		},
   249  	}
   250  }
   251  
   252  func rowToChunk(r *row, c *chunk.Chunk) error {
   253  	decodeCtx := chunk.NewDecodeContext()
   254  	return c.Decode(decodeCtx, []byte(rawUrlDecode(r.Cells[0].Value)))
   255  }