Merge branch 'main' of https://git.tornberg.me/mats/go-cart-actor
This commit is contained in:
@@ -29,7 +29,7 @@ func (a *App) HandleCheckoutRequests(amqpUrl string, mux *http.ServeMux) {
|
||||
orderHandler := NewAmqpOrderHandler(conn)
|
||||
orderHandler.DefineTopics()
|
||||
|
||||
mux.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) {
|
||||
mux.HandleFunc("POST /push", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
@@ -59,7 +59,7 @@ func (a *App) HandleCheckoutRequests(amqpUrl string, mux *http.ServeMux) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = a.klarnaClient.AcknowledgeOrder(orderId)
|
||||
err = a.klarnaClient.AcknowledgeOrder(r.Context(), orderId)
|
||||
if err != nil {
|
||||
log.Printf("Error acknowledging order: %v\n", err)
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func (a *App) HandleCheckoutRequests(amqpUrl string, mux *http.ServeMux) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/checkout", a.server.CheckoutHandler(func(order *CheckoutOrder, w http.ResponseWriter) error {
|
||||
mux.HandleFunc("GET /checkout", a.server.CheckoutHandler(func(order *CheckoutOrder, w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Permissions-Policy", "payment=(self \"https://js.stripe.com\" \"https://m.stripe.network\" \"https://js.playground.kustom.co\")")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -75,7 +75,7 @@ func (a *App) HandleCheckoutRequests(amqpUrl string, mux *http.ServeMux) {
|
||||
return err
|
||||
}))
|
||||
|
||||
mux.HandleFunc("/confirmation/{order_id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
mux.HandleFunc("GET /confirmation/{order_id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
orderId := r.PathValue("order_id")
|
||||
order, err := a.klarnaClient.GetOrder(orderId)
|
||||
@@ -102,7 +102,7 @@ func (a *App) HandleCheckoutRequests(amqpUrl string, mux *http.ServeMux) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, tpl, order.HTMLSnippet)
|
||||
})
|
||||
mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) {
|
||||
mux.HandleFunc("POST /validate", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Klarna order validation, method: %s", r.Method)
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -64,13 +65,13 @@ func (k *KlarnaClient) getOrderResponse(res *http.Response) (*CheckoutOrder, err
|
||||
return nil, fmt.Errorf("%s", res.Status)
|
||||
}
|
||||
|
||||
func (k *KlarnaClient) CreateOrder(reader io.Reader) (*CheckoutOrder, error) {
|
||||
func (k *KlarnaClient) CreateOrder(ctx context.Context, reader io.Reader) (*CheckoutOrder, error) {
|
||||
//bytes.NewReader(reply.Payload)
|
||||
req, err := http.NewRequest("POST", k.Url+"/checkout/v3/orders", reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.WithContext(ctx)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.SetBasicAuth(k.UserName, k.Password)
|
||||
|
||||
@@ -82,13 +83,14 @@ func (k *KlarnaClient) CreateOrder(reader io.Reader) (*CheckoutOrder, error) {
|
||||
return k.getOrderResponse(res)
|
||||
}
|
||||
|
||||
func (k *KlarnaClient) UpdateOrder(orderId string, reader io.Reader) (*CheckoutOrder, error) {
|
||||
func (k *KlarnaClient) UpdateOrder(ctx context.Context, orderId string, reader io.Reader) (*CheckoutOrder, error) {
|
||||
//bytes.NewReader(reply.Payload)
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s/checkout/v3/orders/%s", k.Url, orderId), reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.WithContext(ctx)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.SetBasicAuth(k.UserName, k.Password)
|
||||
|
||||
@@ -100,12 +102,12 @@ func (k *KlarnaClient) UpdateOrder(orderId string, reader io.Reader) (*CheckoutO
|
||||
return k.getOrderResponse(res)
|
||||
}
|
||||
|
||||
func (k *KlarnaClient) AbortOrder(orderId string) error {
|
||||
func (k *KlarnaClient) AbortOrder(ctx context.Context, orderId string) error {
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s/checkout/v3/orders/%s/abort", k.Url, orderId), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.WithContext(ctx)
|
||||
req.SetBasicAuth(k.UserName, k.Password)
|
||||
|
||||
_, err = http.DefaultClient.Do(req)
|
||||
@@ -113,11 +115,12 @@ func (k *KlarnaClient) AbortOrder(orderId string) error {
|
||||
}
|
||||
|
||||
// ordermanagement/v1/orders/{order_id}/acknowledge
|
||||
func (k *KlarnaClient) AcknowledgeOrder(orderId string) error {
|
||||
func (k *KlarnaClient) AcknowledgeOrder(ctx context.Context, orderId string) error {
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%s/ordermanagement/v1/orders/%s/acknowledge", k.Url, orderId), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.WithContext(ctx)
|
||||
id := uuid.New()
|
||||
|
||||
req.SetBasicAuth(k.UserName, k.Password)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.tornberg.me/go-cart-actor/pkg/actor"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -158,7 +160,15 @@ func main() {
|
||||
// go diskStorage.SaveLoop(10 * time.Second)
|
||||
UseDiscovery(pool)
|
||||
|
||||
mux.Handle("/cart/", http.StripPrefix("/cart", syncedServer.Serve()))
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer stop()
|
||||
|
||||
otelShutdown, err := setupOTelSDK(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to start otel %v", err)
|
||||
}
|
||||
|
||||
syncedServer.Serve(mux)
|
||||
// only for local
|
||||
mux.HandleFunc("GET /add/remote/{host}", func(w http.ResponseWriter, r *http.Request) {
|
||||
pool.AddRemote(r.PathValue("host"))
|
||||
@@ -202,23 +212,41 @@ func main() {
|
||||
|
||||
mux.HandleFunc("/openapi.json", ServeEmbeddedOpenAPI)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
done := make(chan bool, 1)
|
||||
signal.Notify(sigs, syscall.SIGTERM)
|
||||
srv := &http.Server{
|
||||
Addr: ":8080",
|
||||
BaseContext: func(net.Listener) context.Context { return ctx },
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 20 * time.Second,
|
||||
Handler: otelhttp.NewHandler(mux, "/"),
|
||||
}
|
||||
|
||||
go func() {
|
||||
sig := <-sigs
|
||||
fmt.Println("Shutting down due to signal:", sig)
|
||||
defer func() {
|
||||
|
||||
fmt.Println("Shutting down due to signal")
|
||||
otelShutdown(context.Background())
|
||||
diskStorage.Close()
|
||||
pool.Close()
|
||||
|
||||
done <- true
|
||||
}()
|
||||
|
||||
srvErr := make(chan error, 1)
|
||||
go func() {
|
||||
srvErr <- srv.ListenAndServe()
|
||||
}()
|
||||
|
||||
log.Print("Server started at port 8080")
|
||||
go http.ListenAndServe(":8080", mux)
|
||||
|
||||
go http.ListenAndServe(":8081", debugMux)
|
||||
<-done
|
||||
|
||||
select {
|
||||
case err = <-srvErr:
|
||||
// Error when starting HTTP server.
|
||||
log.Fatalf("Unable to start server: %v", err)
|
||||
case <-ctx.Done():
|
||||
// Wait for first CTRL+C.
|
||||
// Stop receiving signal notifications as soon as possible.
|
||||
stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
117
cmd/cart/otel.go
Normal file
117
cmd/cart/otel.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
"go.opentelemetry.io/otel/log/global"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/sdk/log"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
// setupOTelSDK bootstraps the OpenTelemetry pipeline.
|
||||
// If it does not return an error, make sure to call shutdown for proper cleanup.
|
||||
func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
|
||||
var shutdownFuncs []func(context.Context) error
|
||||
var err error
|
||||
|
||||
// shutdown calls cleanup functions registered via shutdownFuncs.
|
||||
// The errors from the calls are joined.
|
||||
// Each registered cleanup will be invoked once.
|
||||
shutdown := func(ctx context.Context) error {
|
||||
var err error
|
||||
for _, fn := range shutdownFuncs {
|
||||
err = errors.Join(err, fn(ctx))
|
||||
}
|
||||
shutdownFuncs = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// handleErr calls shutdown for cleanup and makes sure that all errors are returned.
|
||||
handleErr := func(inErr error) {
|
||||
err = errors.Join(inErr, shutdown(ctx))
|
||||
}
|
||||
|
||||
// Set up propagator.
|
||||
prop := newPropagator()
|
||||
otel.SetTextMapPropagator(prop)
|
||||
|
||||
// Set up trace provider.
|
||||
tracerProvider, err := newTracerProvider()
|
||||
if err != nil {
|
||||
handleErr(err)
|
||||
return shutdown, err
|
||||
}
|
||||
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
|
||||
otel.SetTracerProvider(tracerProvider)
|
||||
|
||||
// Set up meter provider.
|
||||
meterProvider, err := newMeterProvider()
|
||||
if err != nil {
|
||||
handleErr(err)
|
||||
return shutdown, err
|
||||
}
|
||||
shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
|
||||
otel.SetMeterProvider(meterProvider)
|
||||
|
||||
// Set up logger provider.
|
||||
loggerProvider, err := newLoggerProvider()
|
||||
if err != nil {
|
||||
handleErr(err)
|
||||
return shutdown, err
|
||||
}
|
||||
shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown)
|
||||
global.SetLoggerProvider(loggerProvider)
|
||||
|
||||
return shutdown, err
|
||||
}
|
||||
|
||||
func newPropagator() propagation.TextMapPropagator {
|
||||
return propagation.NewCompositeTextMapPropagator(
|
||||
propagation.TraceContext{},
|
||||
propagation.Baggage{},
|
||||
)
|
||||
}
|
||||
|
||||
func newTracerProvider() (*trace.TracerProvider, error) {
|
||||
traceExporter, err := otlptracegrpc.New(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tracerProvider := trace.NewTracerProvider(
|
||||
trace.WithBatcher(traceExporter,
|
||||
// Default is 5s. Set to 1s for demonstrative purposes.
|
||||
trace.WithBatchTimeout(time.Second)),
|
||||
)
|
||||
return tracerProvider, nil
|
||||
}
|
||||
|
||||
func newMeterProvider() (*metric.MeterProvider, error) {
|
||||
exporter, err := otlpmetricgrpc.New(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(exporter)))
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func newLoggerProvider() (*log.LoggerProvider, error) {
|
||||
logExporter, err := otlploggrpc.New(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loggerProvider := log.NewLoggerProvider(
|
||||
log.WithProcessor(log.NewBatchProcessor(logExporter)),
|
||||
)
|
||||
return loggerProvider, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -17,6 +18,13 @@ import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -59,7 +67,7 @@ func (s *PoolServer) GetCartHandler(w http.ResponseWriter, r *http.Request, id c
|
||||
|
||||
func (s *PoolServer) AddSkuToCartHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error {
|
||||
sku := r.PathValue("sku")
|
||||
msg, err := GetItemAddMessage(sku, 1, getCountryFromHost(r.Host), nil)
|
||||
msg, err := GetItemAddMessage(r.Context(), sku, 1, getCountryFromHost(r.Host), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -191,14 +199,14 @@ type SetCartItems struct {
|
||||
Items []Item `json:"items"`
|
||||
}
|
||||
|
||||
func getMultipleAddMessages(items []Item, country string) []proto.Message {
|
||||
func getMultipleAddMessages(ctx context.Context, items []Item, country string) []proto.Message {
|
||||
wg := sync.WaitGroup{}
|
||||
mu := sync.Mutex{}
|
||||
msgs := make([]proto.Message, 0, len(items))
|
||||
for _, itm := range items {
|
||||
wg.Go(
|
||||
func() {
|
||||
msg, err := GetItemAddMessage(itm.Sku, itm.Quantity, country, itm.StoreId)
|
||||
msg, err := GetItemAddMessage(ctx, itm.Sku, itm.Quantity, country, itm.StoreId)
|
||||
if err != nil {
|
||||
log.Printf("error adding item %s: %v", itm.Sku, err)
|
||||
return
|
||||
@@ -221,7 +229,7 @@ func (s *PoolServer) SetCartItemsHandler(w http.ResponseWriter, r *http.Request,
|
||||
|
||||
msgs := make([]proto.Message, 0, len(setCartItems.Items)+1)
|
||||
msgs = append(msgs, &messages.ClearCartRequest{})
|
||||
msgs = append(msgs, getMultipleAddMessages(setCartItems.Items, setCartItems.Country)...)
|
||||
msgs = append(msgs, getMultipleAddMessages(r.Context(), setCartItems.Items, setCartItems.Country)...)
|
||||
|
||||
reply, err := s.ApplyLocal(id, msgs...)
|
||||
if err != nil {
|
||||
@@ -237,7 +245,7 @@ func (s *PoolServer) AddMultipleItemHandler(w http.ResponseWriter, r *http.Reque
|
||||
return err
|
||||
}
|
||||
|
||||
reply, err := s.ApplyLocal(id, getMultipleAddMessages(setCartItems.Items, setCartItems.Country)...)
|
||||
reply, err := s.ApplyLocal(id, getMultipleAddMessages(r.Context(), setCartItems.Items, setCartItems.Country)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -257,7 +265,7 @@ func (s *PoolServer) AddSkuRequestHandler(w http.ResponseWriter, r *http.Request
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg, err := GetItemAddMessage(addRequest.Sku, int(addRequest.Quantity), addRequest.Country, addRequest.StoreId)
|
||||
msg, err := GetItemAddMessage(r.Context(), addRequest.Sku, int(addRequest.Quantity), addRequest.Country, addRequest.StoreId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,7 +310,7 @@ func getLocale(country string) string {
|
||||
return "sv-se"
|
||||
}
|
||||
|
||||
func (s *PoolServer) CreateOrUpdateCheckout(host string, id cart.CartId) (*CheckoutOrder, error) {
|
||||
func (s *PoolServer) CreateOrUpdateCheckout(ctx context.Context, host string, id cart.CartId) (*CheckoutOrder, error) {
|
||||
country := getCountryFromHost(host)
|
||||
meta := &CheckoutMeta{
|
||||
Terms: fmt.Sprintf("https://%s/terms", host),
|
||||
@@ -328,9 +336,9 @@ func (s *PoolServer) CreateOrUpdateCheckout(host string, id cart.CartId) (*Check
|
||||
}
|
||||
|
||||
if grain.OrderReference != "" {
|
||||
return s.klarnaClient.UpdateOrder(grain.OrderReference, bytes.NewReader(payload))
|
||||
return s.klarnaClient.UpdateOrder(ctx, grain.OrderReference, bytes.NewReader(payload))
|
||||
} else {
|
||||
return s.klarnaClient.CreateOrder(bytes.NewReader(payload))
|
||||
return s.klarnaClient.CreateOrder(ctx, bytes.NewReader(payload))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,18 +457,49 @@ func CartIdHandler(fn func(cartId cart.CartId, w http.ResponseWriter, r *http.Re
|
||||
func (s *PoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error) func(cartId cart.CartId, w http.ResponseWriter, r *http.Request) error {
|
||||
return func(cartId cart.CartId, w http.ResponseWriter, r *http.Request) error {
|
||||
if ownerHost, ok := s.OwnerHost(uint64(cartId)); ok {
|
||||
ctx, span := tracer.Start(r.Context(), "proxy")
|
||||
defer span.End()
|
||||
span.SetAttributes(attribute.String("cartid", cartId.String()))
|
||||
hostAttr := attribute.String("other host", ownerHost.Name())
|
||||
span.SetAttributes(hostAttr)
|
||||
logger.InfoContext(ctx, "cart proxyed", "result", ownerHost.Name())
|
||||
proxyCalls.Add(ctx, 1, metric.WithAttributes(hostAttr))
|
||||
handled, err := ownerHost.Proxy(uint64(cartId), w, r)
|
||||
|
||||
grainLookups.Inc()
|
||||
if err == nil && handled {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
_, span := tracer.Start(r.Context(), "own")
|
||||
span.SetAttributes(attribute.String("cartid", cartId.String()))
|
||||
defer span.End()
|
||||
return fn(w, r, cartId)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
tracer = otel.Tracer(name)
|
||||
|
||||
meter = otel.Meter(name)
|
||||
logger = otelslog.NewLogger(name)
|
||||
proxyCalls metric.Int64Counter
|
||||
|
||||
// rollCnt metric.Int64Counter
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
proxyCalls, err = meter.Int64Counter("proxy.calls",
|
||||
metric.WithDescription("Number of proxy calls"),
|
||||
metric.WithUnit("{calls}"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type AddVoucherRequest struct {
|
||||
VoucherCode string `json:"code"`
|
||||
}
|
||||
@@ -511,7 +550,7 @@ func (s *PoolServer) CheckoutHandler(fn func(order *CheckoutOrder, w http.Respon
|
||||
return CookieCartIdHandler(s.ProxyHandler(func(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error {
|
||||
orderId := r.URL.Query().Get("order_id")
|
||||
if orderId == "" {
|
||||
order, err := s.CreateOrUpdateCheckout(r.Host, cartId)
|
||||
order, err := s.CreateOrUpdateCheckout(r.Context(), r.Host, cartId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -545,46 +584,57 @@ func (s *PoolServer) RemoveVoucherHandler(w http.ResponseWriter, r *http.Request
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PoolServer) Serve() *http.ServeMux {
|
||||
func (s *PoolServer) Serve(mux *http.ServeMux) {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("OPTIONS /", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
// mux.HandleFunc("OPTIONS /cart", func(w http.ResponseWriter, r *http.Request) {
|
||||
// w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
// w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE")
|
||||
// w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
// w.WriteHeader(http.StatusOK)
|
||||
// })
|
||||
|
||||
mux.HandleFunc("GET /", CookieCartIdHandler(s.ProxyHandler(s.GetCartHandler)))
|
||||
mux.HandleFunc("GET /add/{sku}", CookieCartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler)))
|
||||
mux.HandleFunc("POST /add", CookieCartIdHandler(s.ProxyHandler(s.AddMultipleItemHandler)))
|
||||
mux.HandleFunc("POST /", CookieCartIdHandler(s.ProxyHandler(s.AddSkuRequestHandler)))
|
||||
mux.HandleFunc("POST /set", CookieCartIdHandler(s.ProxyHandler(s.SetCartItemsHandler)))
|
||||
mux.HandleFunc("DELETE /{itemId}", CookieCartIdHandler(s.ProxyHandler(s.DeleteItemHandler)))
|
||||
mux.HandleFunc("PUT /", CookieCartIdHandler(s.ProxyHandler(s.QuantityChangeHandler)))
|
||||
mux.HandleFunc("DELETE /", CookieCartIdHandler(s.ProxyHandler(s.RemoveCartCookie)))
|
||||
mux.HandleFunc("POST /delivery", CookieCartIdHandler(s.ProxyHandler(s.SetDeliveryHandler)))
|
||||
mux.HandleFunc("DELETE /delivery/{deliveryId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveDeliveryHandler)))
|
||||
mux.HandleFunc("PUT /delivery/{deliveryId}/pickupPoint", CookieCartIdHandler(s.ProxyHandler(s.SetPickupPointHandler)))
|
||||
mux.HandleFunc("PUT /voucher", CookieCartIdHandler(s.ProxyHandler(s.AddVoucherHandler)))
|
||||
mux.HandleFunc("PUT /subscription-details", CookieCartIdHandler(s.ProxyHandler(s.SubscriptionDetailsHandler)))
|
||||
mux.HandleFunc("DELETE /voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler)))
|
||||
handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
|
||||
attr := attribute.String("http.route", pattern)
|
||||
mux.HandleFunc(pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
span := trace.SpanFromContext(r.Context())
|
||||
span.SetAttributes(attr)
|
||||
|
||||
//mux.HandleFunc("GET /checkout", CookieCartIdHandler(s.ProxyHandler(s.HandleCheckout)))
|
||||
//mux.HandleFunc("GET /confirmation/{orderId}", CookieCartIdHandler(s.ProxyHandler(s.HandleConfirmation)))
|
||||
labeler, _ := otelhttp.LabelerFromContext(r.Context())
|
||||
labeler.Add(attr)
|
||||
|
||||
mux.HandleFunc("GET /byid/{id}", CartIdHandler(s.ProxyHandler(s.GetCartHandler)))
|
||||
mux.HandleFunc("GET /byid/{id}/add/{sku}", CartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler)))
|
||||
mux.HandleFunc("POST /byid/{id}", CartIdHandler(s.ProxyHandler(s.AddSkuRequestHandler)))
|
||||
mux.HandleFunc("DELETE /byid/{id}/{itemId}", CartIdHandler(s.ProxyHandler(s.DeleteItemHandler)))
|
||||
mux.HandleFunc("PUT /byid/{id}", CartIdHandler(s.ProxyHandler(s.QuantityChangeHandler)))
|
||||
mux.HandleFunc("POST /byid/{id}/delivery", CartIdHandler(s.ProxyHandler(s.SetDeliveryHandler)))
|
||||
mux.HandleFunc("DELETE /byid/{id}/delivery/{deliveryId}", CartIdHandler(s.ProxyHandler(s.RemoveDeliveryHandler)))
|
||||
mux.HandleFunc("PUT /byid/{id}/delivery/{deliveryId}/pickupPoint", CartIdHandler(s.ProxyHandler(s.SetPickupPointHandler)))
|
||||
mux.HandleFunc("PUT /byid/{id}/voucher", CookieCartIdHandler(s.ProxyHandler(s.AddVoucherHandler)))
|
||||
mux.HandleFunc("DELETE /byid/{id}/voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler)))
|
||||
//mux.HandleFunc("GET /byid/{id}/checkout", CartIdHandler(s.ProxyHandler(s.HandleCheckout)))
|
||||
//mux.HandleFunc("GET /byid/{id}/confirmation", CartIdHandler(s.ProxyHandler(s.HandleConfirmation)))
|
||||
handlerFunc(w, r)
|
||||
}))
|
||||
}
|
||||
|
||||
handleFunc("GET /cart", CookieCartIdHandler(s.ProxyHandler(s.GetCartHandler)))
|
||||
handleFunc("GET /cart/add/{sku}", CookieCartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler)))
|
||||
handleFunc("POST /cart/add", CookieCartIdHandler(s.ProxyHandler(s.AddMultipleItemHandler)))
|
||||
handleFunc("POST /cart", CookieCartIdHandler(s.ProxyHandler(s.AddSkuRequestHandler)))
|
||||
handleFunc("POST /cart/set", CookieCartIdHandler(s.ProxyHandler(s.SetCartItemsHandler)))
|
||||
handleFunc("DELETE /cart/{itemId}", CookieCartIdHandler(s.ProxyHandler(s.DeleteItemHandler)))
|
||||
handleFunc("PUT /cart", CookieCartIdHandler(s.ProxyHandler(s.QuantityChangeHandler)))
|
||||
handleFunc("DELETE /cart", CookieCartIdHandler(s.ProxyHandler(s.RemoveCartCookie)))
|
||||
handleFunc("POST /cart/delivery", CookieCartIdHandler(s.ProxyHandler(s.SetDeliveryHandler)))
|
||||
handleFunc("DELETE /cart/delivery/{deliveryId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveDeliveryHandler)))
|
||||
handleFunc("PUT /cart/delivery/{deliveryId}/pickupPoint", CookieCartIdHandler(s.ProxyHandler(s.SetPickupPointHandler)))
|
||||
handleFunc("PUT /cart/voucher", CookieCartIdHandler(s.ProxyHandler(s.AddVoucherHandler)))
|
||||
handleFunc("PUT /cart/subscription-details", CookieCartIdHandler(s.ProxyHandler(s.SubscriptionDetailsHandler)))
|
||||
handleFunc("DELETE /cart/voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler)))
|
||||
|
||||
//mux.HandleFunc("GET /cart/checkout", CookieCartIdHandler(s.ProxyHandler(s.HandleCheckout)))
|
||||
//mux.HandleFunc("GET /cart/confirmation/{orderId}", CookieCartIdHandler(s.ProxyHandler(s.HandleConfirmation)))
|
||||
|
||||
handleFunc("GET /cart/byid/{id}", CartIdHandler(s.ProxyHandler(s.GetCartHandler)))
|
||||
handleFunc("GET /cart/byid/{id}/add/{sku}", CartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler)))
|
||||
handleFunc("POST /cart/byid/{id}", CartIdHandler(s.ProxyHandler(s.AddSkuRequestHandler)))
|
||||
handleFunc("DELETE /cart/byid/{id}/{itemId}", CartIdHandler(s.ProxyHandler(s.DeleteItemHandler)))
|
||||
handleFunc("PUT /cart/byid/{id}", CartIdHandler(s.ProxyHandler(s.QuantityChangeHandler)))
|
||||
handleFunc("POST /cart/byid/{id}/delivery", CartIdHandler(s.ProxyHandler(s.SetDeliveryHandler)))
|
||||
handleFunc("DELETE /cart/byid/{id}/delivery/{deliveryId}", CartIdHandler(s.ProxyHandler(s.RemoveDeliveryHandler)))
|
||||
handleFunc("PUT /cart/byid/{id}/delivery/{deliveryId}/pickupPoint", CartIdHandler(s.ProxyHandler(s.SetPickupPointHandler)))
|
||||
handleFunc("PUT /cart/byid/{id}/voucher", CookieCartIdHandler(s.ProxyHandler(s.AddVoucherHandler)))
|
||||
handleFunc("DELETE /cart/byid/{id}/voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler)))
|
||||
//mux.HandleFunc("GET /cart/byid/{id}/checkout", CartIdHandler(s.ProxyHandler(s.HandleCheckout)))
|
||||
//mux.HandleFunc("GET /cart/byid/{id}/confirmation", CartIdHandler(s.ProxyHandler(s.HandleConfirmation)))
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -26,9 +27,14 @@ func getBaseUrl(country string) string {
|
||||
return "http://localhost:8082"
|
||||
}
|
||||
|
||||
func FetchItem(sku string, country string) (*index.DataItem, error) {
|
||||
func FetchItem(ctx context.Context, sku string, country string) (*index.DataItem, error) {
|
||||
baseUrl := getBaseUrl(country)
|
||||
res, err := http.Get(fmt.Sprintf("%s/api/by-sku/%s", baseUrl, sku))
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/by-sku/%s", baseUrl, sku), nil)
|
||||
req = req.WithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,8 +44,8 @@ func FetchItem(sku string, country string) (*index.DataItem, error) {
|
||||
return &item, err
|
||||
}
|
||||
|
||||
func GetItemAddMessage(sku string, qty int, country string, storeId *string) (*messages.AddItem, error) {
|
||||
item, err := FetchItem(sku, country)
|
||||
func GetItemAddMessage(ctx context.Context, sku string, qty int, country string, storeId *string) (*messages.AddItem, error) {
|
||||
item, err := FetchItem(ctx, sku, country)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -44,18 +44,25 @@ func main() {
|
||||
s.UpdateInventory(p, "4", "4", 40)
|
||||
return nil
|
||||
})
|
||||
err = s.ReserveInventory(inventory.ReserveRequest{
|
||||
err = s.ReserveInventory(
|
||||
inventory.ReserveRequest{
|
||||
SKU: "1",
|
||||
LocationID: "1",
|
||||
Quantity: 3,
|
||||
}, inventory.ReserveRequest{
|
||||
},
|
||||
inventory.ReserveRequest{
|
||||
SKU: "2",
|
||||
LocationID: "2",
|
||||
Quantity: 15,
|
||||
})
|
||||
},
|
||||
inventory.ReserveRequest{
|
||||
SKU: "3",
|
||||
LocationID: "3",
|
||||
Quantity: 25,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Unable to reserve inventory: %v", err)
|
||||
return
|
||||
}
|
||||
v, err := s.GetInventory("1", "1")
|
||||
if err != nil {
|
||||
|
||||
@@ -60,14 +60,14 @@ spec:
|
||||
path: /livez
|
||||
port: web
|
||||
failureThreshold: 1
|
||||
periodSeconds: 10
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: web
|
||||
failureThreshold: 2
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
periodSeconds: 30
|
||||
volumeMounts:
|
||||
- mountPath: "/data"
|
||||
name: data
|
||||
@@ -152,14 +152,14 @@ spec:
|
||||
path: /livez
|
||||
port: web
|
||||
failureThreshold: 1
|
||||
periodSeconds: 10
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: web
|
||||
failureThreshold: 2
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
periodSeconds: 50
|
||||
volumeMounts:
|
||||
- mountPath: "/data"
|
||||
name: data
|
||||
@@ -177,6 +177,10 @@ spec:
|
||||
secretKeyRef:
|
||||
name: klarna-api-credentials
|
||||
key: username
|
||||
- name: OTEL_RESOURCE_ATTRIBUTES
|
||||
value: "service.name=cart,service.version=0.1.2"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "http://otel-debug-service.monitoring:4317"
|
||||
- name: KLARNA_API_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@@ -256,14 +260,14 @@ spec:
|
||||
path: /livez
|
||||
port: web
|
||||
failureThreshold: 1
|
||||
periodSeconds: 10
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: web
|
||||
failureThreshold: 2
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
periodSeconds: 30
|
||||
volumeMounts:
|
||||
- mountPath: "/data"
|
||||
name: data
|
||||
@@ -276,6 +280,10 @@ spec:
|
||||
env:
|
||||
- name: TZ
|
||||
value: "Europe/Stockholm"
|
||||
- name: OTEL_RESOURCE_ATTRIBUTES
|
||||
value: "service.name=cart,service.version=0.1.2"
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "http://otel-debug-service.monitoring:4317"
|
||||
- name: KLARNA_API_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
||||
20
go.mod
20
go.mod
@@ -9,6 +9,18 @@ require (
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/redis/go-redis/v9 v9.16.0
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
|
||||
go.opentelemetry.io/otel/log v0.14.0
|
||||
go.opentelemetry.io/otel/metric v1.38.0
|
||||
go.opentelemetry.io/otel/sdk v1.38.0
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
k8s.io/api v0.34.1
|
||||
@@ -20,14 +32,17 @@ require (
|
||||
github.com/RoaringBitmap/roaring/v2 v2.13.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/getkin/kin-openapi v0.132.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||
github.com/go-openapi/swag v0.25.1 // indirect
|
||||
@@ -45,6 +60,7 @@ require (
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/gorilla/schema v1.4.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
@@ -66,6 +82,9 @@ require (
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
@@ -77,6 +96,7 @@ require (
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
33
go.sum
33
go.sum
@@ -8,6 +8,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
@@ -24,6 +26,8 @@ github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aT
|
||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
@@ -31,6 +35,7 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
|
||||
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
@@ -100,6 +105,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
@@ -176,8 +183,8 @@ github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzuk
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
|
||||
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8=
|
||||
@@ -204,16 +211,36 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s=
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
||||
go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
|
||||
go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
@@ -286,6 +313,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
"time"
|
||||
|
||||
messages "git.tornberg.me/go-cart-actor/pkg/messages"
|
||||
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
@@ -19,9 +23,74 @@ type ControlServer[V any] struct {
|
||||
pool GrainPool[V]
|
||||
}
|
||||
|
||||
const name = "grpc_server"
|
||||
|
||||
var (
|
||||
tracer = otel.Tracer(name)
|
||||
meter = otel.Meter(name)
|
||||
logger = otelslog.NewLogger(name)
|
||||
pingCalls metric.Int64Counter
|
||||
negotiateCalls metric.Int64Counter
|
||||
getLocalActorIdsCalls metric.Int64Counter
|
||||
announceOwnershipCalls metric.Int64Counter
|
||||
announceExpiryCalls metric.Int64Counter
|
||||
closingCalls metric.Int64Counter
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
pingCalls, err = meter.Int64Counter("grpc.ping_calls",
|
||||
metric.WithDescription("Number of ping calls"),
|
||||
metric.WithUnit("{calls}"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
negotiateCalls, err = meter.Int64Counter("grpc.negotiate_calls",
|
||||
metric.WithDescription("Number of negotiate calls"),
|
||||
metric.WithUnit("{calls}"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
getLocalActorIdsCalls, err = meter.Int64Counter("grpc.get_local_actor_ids_calls",
|
||||
metric.WithDescription("Number of get local actor ids calls"),
|
||||
metric.WithUnit("{calls}"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
announceOwnershipCalls, err = meter.Int64Counter("grpc.announce_ownership_calls",
|
||||
metric.WithDescription("Number of announce ownership calls"),
|
||||
metric.WithUnit("{calls}"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
announceExpiryCalls, err = meter.Int64Counter("grpc.announce_expiry_calls",
|
||||
metric.WithDescription("Number of announce expiry calls"),
|
||||
metric.WithUnit("{calls}"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
closingCalls, err = meter.Int64Counter("grpc.closing_calls",
|
||||
metric.WithDescription("Number of closing calls"),
|
||||
metric.WithUnit("{calls}"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ControlServer[V]) AnnounceOwnership(ctx context.Context, req *messages.OwnershipAnnounce) (*messages.OwnerChangeAck, error) {
|
||||
ctx, span := tracer.Start(ctx, "grpc_announce_ownership")
|
||||
defer span.End()
|
||||
span.SetAttributes(
|
||||
attribute.String("component", "controlplane"),
|
||||
attribute.String("host", req.Host),
|
||||
attribute.Int("id_count", len(req.Ids)),
|
||||
)
|
||||
logger.InfoContext(ctx, "announce ownership", "host", req.Host, "id_count", len(req.Ids))
|
||||
announceOwnershipCalls.Add(ctx, 1, metric.WithAttributes(attribute.String("host", req.Host)))
|
||||
|
||||
err := s.pool.HandleOwnershipChange(req.Host, req.Ids)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
return &messages.OwnerChangeAck{
|
||||
Accepted: false,
|
||||
Message: "owner change failed",
|
||||
@@ -36,7 +105,20 @@ func (s *ControlServer[V]) AnnounceOwnership(ctx context.Context, req *messages.
|
||||
}
|
||||
|
||||
func (s *ControlServer[V]) AnnounceExpiry(ctx context.Context, req *messages.ExpiryAnnounce) (*messages.OwnerChangeAck, error) {
|
||||
ctx, span := tracer.Start(ctx, "grpc_announce_expiry")
|
||||
defer span.End()
|
||||
span.SetAttributes(
|
||||
attribute.String("component", "controlplane"),
|
||||
attribute.String("host", req.Host),
|
||||
attribute.Int("id_count", len(req.Ids)),
|
||||
)
|
||||
logger.InfoContext(ctx, "announce expiry", "host", req.Host, "id_count", len(req.Ids))
|
||||
announceExpiryCalls.Add(ctx, 1, metric.WithAttributes(attribute.String("host", req.Host)))
|
||||
|
||||
err := s.pool.HandleRemoteExpiry(req.Host, req.Ids)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
}
|
||||
return &messages.OwnerChangeAck{
|
||||
Accepted: err == nil,
|
||||
Message: "expiry acknowledged",
|
||||
@@ -45,15 +127,28 @@ func (s *ControlServer[V]) AnnounceExpiry(ctx context.Context, req *messages.Exp
|
||||
|
||||
// ControlPlane: Ping
|
||||
func (s *ControlServer[V]) Ping(ctx context.Context, _ *messages.Empty) (*messages.PingReply, error) {
|
||||
|
||||
host := s.pool.Hostname()
|
||||
|
||||
pingCalls.Add(ctx, 1, metric.WithAttributes(attribute.String("host", host)))
|
||||
|
||||
// log.Printf("got ping")
|
||||
return &messages.PingReply{
|
||||
Host: s.pool.Hostname(),
|
||||
Host: host,
|
||||
UnixTime: time.Now().Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ControlPlane: Negotiate (merge host views)
|
||||
func (s *ControlServer[V]) Negotiate(ctx context.Context, req *messages.NegotiateRequest) (*messages.NegotiateReply, error) {
|
||||
ctx, span := tracer.Start(ctx, "grpc_negotiate")
|
||||
defer span.End()
|
||||
span.SetAttributes(
|
||||
attribute.String("component", "controlplane"),
|
||||
attribute.Int("known_hosts_count", len(req.KnownHosts)),
|
||||
)
|
||||
logger.InfoContext(ctx, "negotiate", "known_hosts_count", len(req.KnownHosts))
|
||||
negotiateCalls.Add(ctx, 1)
|
||||
|
||||
s.pool.Negotiate(req.KnownHosts)
|
||||
return &messages.NegotiateReply{Hosts: req.GetKnownHosts()}, nil
|
||||
@@ -61,13 +156,33 @@ func (s *ControlServer[V]) Negotiate(ctx context.Context, req *messages.Negotiat
|
||||
|
||||
// ControlPlane: GetCartIds (locally owned carts only)
|
||||
func (s *ControlServer[V]) GetLocalActorIds(ctx context.Context, _ *messages.Empty) (*messages.ActorIdsReply, error) {
|
||||
return &messages.ActorIdsReply{Ids: s.pool.GetLocalIds()}, nil
|
||||
ctx, span := tracer.Start(ctx, "grpc_get_local_actor_ids")
|
||||
defer span.End()
|
||||
ids := s.pool.GetLocalIds()
|
||||
span.SetAttributes(
|
||||
attribute.String("component", "controlplane"),
|
||||
attribute.Int("id_count", len(ids)),
|
||||
)
|
||||
logger.InfoContext(ctx, "get local actor ids", "id_count", len(ids))
|
||||
getLocalActorIdsCalls.Add(ctx, 1)
|
||||
|
||||
return &messages.ActorIdsReply{Ids: ids}, nil
|
||||
}
|
||||
|
||||
// ControlPlane: Closing (peer shutdown notification)
|
||||
func (s *ControlServer[V]) Closing(ctx context.Context, req *messages.ClosingNotice) (*messages.OwnerChangeAck, error) {
|
||||
if req.GetHost() != "" {
|
||||
s.pool.RemoveHost(req.GetHost())
|
||||
ctx, span := tracer.Start(ctx, "grpc_closing")
|
||||
defer span.End()
|
||||
host := req.GetHost()
|
||||
span.SetAttributes(
|
||||
attribute.String("component", "controlplane"),
|
||||
attribute.String("host", host),
|
||||
)
|
||||
logger.InfoContext(ctx, "closing notice", "host", host)
|
||||
closingCalls.Add(ctx, 1, metric.WithAttributes(attribute.String("host", host)))
|
||||
|
||||
if host != "" {
|
||||
s.pool.RemoveHost(host)
|
||||
}
|
||||
return &messages.OwnerChangeAck{
|
||||
Accepted: true,
|
||||
|
||||
@@ -88,20 +88,50 @@ var (
|
||||
ErrMissingReservation = errors.New("missing reservation")
|
||||
)
|
||||
|
||||
func makeKeysAndArgs(req ...ReserveRequest) ([]string, []string) {
|
||||
keys := make([]string, len(req))
|
||||
args := make([]string, len(req))
|
||||
for i, r := range req {
|
||||
if r.Quantity <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
keys[i] = getInventoryKey(r.SKU, r.LocationID)
|
||||
args[i] = strconv.Itoa(int(r.Quantity))
|
||||
}
|
||||
return keys, args
|
||||
}
|
||||
|
||||
func (s *RedisInventoryService) ReservationCheck(req ...ReserveRequest) error {
|
||||
if len(req) == 0 {
|
||||
return ErrMissingReservation
|
||||
}
|
||||
|
||||
keys, args := makeKeysAndArgs(req...)
|
||||
if keys == nil || args == nil {
|
||||
return ErrInvalidQuantity
|
||||
}
|
||||
|
||||
cmd := reservationCheck.Run(s.ctx, s.client, keys, args)
|
||||
if err := cmd.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if val, err := cmd.Int(); err != nil {
|
||||
return err
|
||||
} else if val != 1 {
|
||||
return ErrInsufficientInventory
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RedisInventoryService) ReserveInventory(req ...ReserveRequest) error {
|
||||
if len(req) == 0 {
|
||||
return ErrMissingReservation
|
||||
}
|
||||
|
||||
keys := make([]string, len(req))
|
||||
args := make([]string, len(req))
|
||||
for i, r := range req {
|
||||
if r.Quantity <= 0 {
|
||||
keys, args := makeKeysAndArgs(req...)
|
||||
if keys == nil || args == nil {
|
||||
return ErrInvalidQuantity
|
||||
}
|
||||
keys[i] = getInventoryKey(r.SKU, r.LocationID)
|
||||
args[i] = strconv.Itoa(int(r.Quantity))
|
||||
}
|
||||
cmd := reserveScript.Run(s.ctx, s.client, keys, args)
|
||||
if err := cmd.Err(); err != nil {
|
||||
return err
|
||||
@@ -114,6 +144,47 @@ func (s *RedisInventoryService) ReserveInventory(req ...ReserveRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var reservationCheck = redis.NewScript(`
|
||||
-- Get the number of keys passed
|
||||
local num_keys = #KEYS
|
||||
|
||||
-- Ensure the number of keys matches the number of quantities
|
||||
if num_keys ~= #ARGV then
|
||||
return {err = "Script requires the same number of keys and quantities."}
|
||||
end
|
||||
|
||||
local new_values = {}
|
||||
local payload = {}
|
||||
|
||||
-- ---
|
||||
-- 1. CHECK PHASE
|
||||
-- ---
|
||||
-- Loop through all keys to check their values first
|
||||
for i = 1, num_keys do
|
||||
local key = KEYS[i]
|
||||
local quantity_to_check = tonumber(ARGV[i])
|
||||
|
||||
-- Fail if the quantity is not a valid number
|
||||
if not quantity_to_check then
|
||||
return {err = "Invalid quantity provided for key: " .. key}
|
||||
end
|
||||
|
||||
-- Get the current value stored at the key
|
||||
local current_val = tonumber(redis.call('GET', key))
|
||||
|
||||
-- Check the condition
|
||||
-- Fail if:
|
||||
-- 1. The key doesn't exist (current_val is nil)
|
||||
-- 2. The value is not > the required quantity
|
||||
if not current_val or current_val <= quantity_to_check then
|
||||
-- Return 0 to indicate the operation failed and no changes were made
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
return 1
|
||||
`)
|
||||
|
||||
var reserveScript = redis.NewScript(`
|
||||
-- Get the number of keys passed
|
||||
local num_keys = #KEYS
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
messages "git.tornberg.me/go-cart-actor/pkg/messages"
|
||||
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
@@ -26,6 +29,14 @@ type RemoteHost struct {
|
||||
missedPings int
|
||||
}
|
||||
|
||||
const name = "proxy"
|
||||
|
||||
var (
|
||||
tracer = otel.Tracer(name)
|
||||
meter = otel.Meter(name)
|
||||
logger = otelslog.NewLogger(name)
|
||||
)
|
||||
|
||||
func NewRemoteHost(host string) (*RemoteHost, error) {
|
||||
|
||||
target := fmt.Sprintf("%s:1337", host)
|
||||
@@ -49,7 +60,7 @@ func NewRemoteHost(host string) (*RemoteHost, error) {
|
||||
|
||||
return &RemoteHost{
|
||||
host: host,
|
||||
httpBase: fmt.Sprintf("http://%s:8080/cart", host),
|
||||
httpBase: fmt.Sprintf("http://%s:8080", host),
|
||||
conn: conn,
|
||||
transport: transport,
|
||||
client: client,
|
||||
@@ -146,8 +157,22 @@ func (h *RemoteHost) AnnounceExpiry(uids []uint64) {
|
||||
func (h *RemoteHost) Proxy(id uint64, w http.ResponseWriter, r *http.Request) (bool, error) {
|
||||
target := fmt.Sprintf("%s%s", h.httpBase, r.URL.RequestURI())
|
||||
|
||||
req, err := http.NewRequestWithContext(r.Context(), r.Method, target, r.Body)
|
||||
log.Printf("proxy target: %s, method: %s", target, r.Method)
|
||||
|
||||
ctx, span := tracer.Start(r.Context(), "remote_proxy")
|
||||
defer span.End()
|
||||
span.SetAttributes(
|
||||
attribute.String("component", "proxy"),
|
||||
attribute.String("cartid", fmt.Sprintf("%d", id)),
|
||||
attribute.String("host", h.host),
|
||||
attribute.String("method", r.Method),
|
||||
attribute.String("target", target),
|
||||
)
|
||||
logger.InfoContext(ctx, "proxying request", "cartid", id, "host", h.host, "method", r.Method)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, r.Method, target, r.Body)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
http.Error(w, "proxy build error", http.StatusBadGateway)
|
||||
return false, err
|
||||
}
|
||||
@@ -161,10 +186,12 @@ func (h *RemoteHost) Proxy(id uint64, w http.ResponseWriter, r *http.Request) (b
|
||||
}
|
||||
res, err := h.client.Do(req)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
http.Error(w, "proxy request error", http.StatusBadGateway)
|
||||
return false, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
span.SetAttributes(attribute.Int("status_code", res.StatusCode))
|
||||
for k, v := range res.Header {
|
||||
for _, vv := range v {
|
||||
w.Header().Add(k, vv)
|
||||
@@ -175,6 +202,7 @@ func (h *RemoteHost) Proxy(id uint64, w http.ResponseWriter, r *http.Request) (b
|
||||
w.WriteHeader(res.StatusCode)
|
||||
_, copyErr := io.Copy(w, res.Body)
|
||||
if copyErr != nil {
|
||||
span.RecordError(copyErr)
|
||||
return true, copyErr
|
||||
}
|
||||
return true, nil
|
||||
|
||||
Reference in New Issue
Block a user