Compare commits
116 Commits
9aa64b2808
...
refactor/g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b97eb8f285 | ||
|
|
2697832d98 | ||
|
|
4c973b239f | ||
|
|
f735540c3d | ||
| 92ebb49b09 | |||
| d98122756a | |||
| b4c9d09657 | |||
| fec181f640 | |||
| 8c8ff75b2d | |||
| 5c7b8d87e8 | |||
| 25cf3f8005 | |||
|
|
9e4d5df733 | ||
|
|
08064f958a | ||
|
|
b8b79dfb35 | ||
|
|
d2218cca37 | ||
|
|
81898648f5 | ||
|
|
e87ec1a8c1 | ||
|
|
ed7b025134 | ||
|
|
c090a23c4d | ||
|
|
7a5f1e3609 | ||
|
|
8d26c35f61 | ||
|
|
045c15b41a | ||
|
|
00b465a8c9 | ||
|
|
54037741be | ||
|
|
2f04c1d754 | ||
|
|
4cb181578a | ||
|
|
79fc990a98 | ||
|
|
ea173260b9 | ||
|
|
f485dfa038 | ||
|
|
cf31f17d57 | ||
|
|
191171e431 | ||
|
|
5ab2818e8d | ||
|
|
82528e35b4 | ||
|
|
954d23df63 | ||
|
|
0f55b86bfd | ||
|
|
5d8a4f8e83 | ||
|
|
9ba5384615 | ||
|
|
f079d0b649 | ||
|
|
ebd54b1476 | ||
|
|
08a4a5c216 | ||
|
|
2af4657b06 | ||
|
|
c4fbc89a0f | ||
|
|
f10769f0a5 | ||
|
|
dfbc2c31d4 | ||
|
|
e88e312150 | ||
|
|
f1d2591a6c | ||
|
|
4056da81ea | ||
|
|
19bbf32e68 | ||
|
|
6c2328495b | ||
|
|
b51fc78dd5 | ||
|
|
d865d8fe10 | ||
|
|
ef9d191625 | ||
|
|
01a64c1fd1 | ||
|
|
f8c629e1bc | ||
|
|
2324caed49 | ||
|
|
8f10b58d94 | ||
|
|
86141c565a | ||
|
|
4cc1851626 | ||
|
|
b63415bc2c | ||
|
|
ee292b30cb | ||
|
|
49cebad861 | ||
|
|
00e39cf7fa | ||
|
|
0408524d41 | ||
|
|
b2dabc1e35 | ||
|
|
9f585eec2e | ||
|
|
7d27257a71 | ||
|
|
1397822a1a | ||
|
|
10c52ac409 | ||
|
|
9fa69225c8 | ||
|
|
d60936c4f8 | ||
|
|
7f0530e990 | ||
|
|
ce40af5bdb | ||
|
|
03123bac89 | ||
| d35a87e549 | |||
|
|
abc887d4fc | ||
|
|
89f8a695f7 | ||
| 5675686f47 | |||
|
|
15e7bc6c6d | ||
|
|
354964040a | ||
|
|
af5b060d09 | ||
|
|
006d3ab4d8 | ||
|
|
9b137e2ff1 | ||
|
|
c17a8a4219 | ||
|
|
933f0bb04f | ||
|
|
3d55c4ed1f | ||
|
|
d5efb39872 | ||
|
|
5348c33f3b | ||
|
|
1f7f161e62 | ||
|
|
d8f364c329 | ||
|
|
85e4a55418 | ||
|
|
b2b431f2de | ||
|
|
4dd303454c | ||
|
|
fcbf4038f3 | ||
|
|
895502824f | ||
|
|
bc8ffd1878 | ||
|
|
21b51cf020 | ||
|
|
238fa5a1e6 | ||
|
|
256511243e | ||
|
|
893abe9ae2 | ||
|
|
86d310c80f | ||
|
|
bb55c5cb6f | ||
|
|
2575634d72 | ||
|
|
95efa0c4d8 | ||
|
|
4c21ab47e9 | ||
|
|
d77b1edacb | ||
|
|
75796e7da1 | ||
|
|
1562b193ff | ||
|
|
dd7f599c24 | ||
|
|
85d2a0a572 | ||
|
|
7bf3e3dccd | ||
|
|
de3d6a8beb | ||
|
|
79b66c216d | ||
|
|
26a3b76ec4 | ||
|
|
3cf8d3c0b8 | ||
|
|
0837be452e | ||
|
|
00d65035d0 |
@@ -12,6 +12,8 @@ jobs:
|
|||||||
run: docker build --progress=plain -t registry.knatofs.se/go-cart-actor-amd64:latest .
|
run: docker build --progress=plain -t registry.knatofs.se/go-cart-actor-amd64:latest .
|
||||||
- name: Push to registry
|
- name: Push to registry
|
||||||
run: docker push registry.knatofs.se/go-cart-actor-amd64:latest
|
run: docker push registry.knatofs.se/go-cart-actor-amd64:latest
|
||||||
|
- name: Deploy to Kubernetes
|
||||||
|
run: kubectl apply -f deployment/deployment.yaml -n cart
|
||||||
- name: Rollout amd64 deployment
|
- name: Rollout amd64 deployment
|
||||||
run: kubectl rollout restart deployment/cart-actor-x86 -n cart
|
run: kubectl rollout restart deployment/cart-actor-x86 -n cart
|
||||||
|
|
||||||
@@ -24,7 +26,5 @@ jobs:
|
|||||||
run: docker build --progress=plain -t registry.knatofs.se/go-cart-actor .
|
run: docker build --progress=plain -t registry.knatofs.se/go-cart-actor .
|
||||||
- name: Push to registry
|
- name: Push to registry
|
||||||
run: docker push registry.knatofs.se/go-cart-actor
|
run: docker push registry.knatofs.se/go-cart-actor
|
||||||
- name: Deploy to Kubernetes
|
|
||||||
run: kubectl apply -f deployment/deployment.yaml -n cart
|
|
||||||
- name: Rollout arm64 deployment
|
- name: Rollout arm64 deployment
|
||||||
run: kubectl rollout restart deployment/cart-actor-arm64 -n cart
|
run: kubectl rollout restart deployment/cart-actor-arm64 -n cart
|
||||||
396
GRPC-MIGRATION-PLAN.md
Normal file
396
GRPC-MIGRATION-PLAN.md
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
# gRPC Migration Plan
|
||||||
|
|
||||||
|
File: GRPC-MIGRATION-PLAN.md
|
||||||
|
Author: (Generated plan)
|
||||||
|
Status: Draft for review
|
||||||
|
Target Release: Next major version (breaking change – no mixed compatibility)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
This document describes the full migration of the current custom TCP frame-based protocol (both the cart mutation/state channel on port `1337` and the control plane on port `1338`) to gRPC. We will remove all legacy packet framing (`FrameWithPayload`, `RemoteGrain`, `GenericListener` handlers for these two ports) and replace them with two gRPC services:
|
||||||
|
|
||||||
|
1. Cart Actor Service (mutations + state retrieval)
|
||||||
|
2. Control Plane Service (cluster membership, negotiation, ownership change, lifecycle)
|
||||||
|
|
||||||
|
We intentionally keep:
|
||||||
|
- Internal `CartGrain` logic, message storage format, disk persistence, and JSON cart serialization.
|
||||||
|
- Existing message type numeric mapping for backward compatibility with persisted event logs.
|
||||||
|
- HTTP/REST API layer unchanged (it still consumes JSON state from the local/remote grain pipeline).
|
||||||
|
|
||||||
|
We do NOT implement mixed-version compatibility; migration occurs atomically (cluster restart with new image).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Goals
|
||||||
|
|
||||||
|
- Remove custom binary frame protocol & simplify maintenance.
|
||||||
|
- Provide clearer, strongly defined interfaces via `.proto` schemas.
|
||||||
|
- Improve observability via gRPC interceptors (metrics & tracing hooks).
|
||||||
|
- Reduce per-call overhead compared with the current manual connection pooling + handwritten framing (HTTP/2 multiplexing + connection reuse).
|
||||||
|
- Prepare groundwork for future enhancements (streaming, typed state, event streaming) without rewriting again.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Non-Goals (Phase 1)
|
||||||
|
|
||||||
|
- Converting the cart state payload from JSON to a strongly typed proto.
|
||||||
|
- Introducing authentication / mTLS (may be added later).
|
||||||
|
- Changing persistence or replay format.
|
||||||
|
- Changing the HTTP API contract.
|
||||||
|
- Implementing streaming watchers or push updates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Architecture After Migration
|
||||||
|
|
||||||
|
Ports:
|
||||||
|
- `:1337` → gRPC CartActor service.
|
||||||
|
- `:1338` → gRPC ControlPlane service.
|
||||||
|
|
||||||
|
Each node:
|
||||||
|
- Runs one gRPC server with both services (can use a single listener bound to two services or keep two separate listeners; we will keep two ports initially to minimize operational surprise, but they could be merged later).
|
||||||
|
- Maintains a connection pool of `*grpc.ClientConn` objects keyed by remote hostname (one per remote host, reused for both services).
|
||||||
|
|
||||||
|
Call Flow (Mutation):
|
||||||
|
1. HTTP request hits `PoolServer`.
|
||||||
|
2. `SyncedPool.getGrain(cartId)`:
|
||||||
|
- Local: direct invocation.
|
||||||
|
- Remote: uses `RemoteGrainGRPC` (new) which invokes `CartActor.Mutate`.
|
||||||
|
3. Response JSON returned unchanged.
|
||||||
|
|
||||||
|
Control Plane Flow:
|
||||||
|
- Discovery (K8s watch) still triggers `AddRemote(host)`.
|
||||||
|
- Instead of custom `Ping`, `Negotiate`, etc. via frames, call gRPC methods on `ControlPlane` service.
|
||||||
|
- Ownership changes use `ConfirmOwner` RPC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Proto Design
|
||||||
|
|
||||||
|
### 5.1 Cart Actor Proto (Envelope Pattern)
|
||||||
|
|
||||||
|
We keep an envelope with `bytes payload` holding the serialized underlying cart mutation proto (existing types in `messages.proto`). This minimizes churn.
|
||||||
|
|
||||||
|
Indented code block (proto sketch):
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
package cart;
|
||||||
|
option go_package = "git.tornberg.me/go-cart-actor/proto;proto";
|
||||||
|
|
||||||
|
enum MutationType {
|
||||||
|
MUTATION_TYPE_UNSPECIFIED = 0;
|
||||||
|
MUTATION_ADD_REQUEST = 1;
|
||||||
|
MUTATION_ADD_ITEM = 2;
|
||||||
|
MUTATION_REMOVE_ITEM = 4;
|
||||||
|
MUTATION_REMOVE_DELIVERY = 5;
|
||||||
|
MUTATION_CHANGE_QUANTITY = 6;
|
||||||
|
MUTATION_SET_DELIVERY = 7;
|
||||||
|
MUTATION_SET_PICKUP_POINT = 8;
|
||||||
|
MUTATION_CREATE_CHECKOUT_ORDER = 9;
|
||||||
|
MUTATION_SET_CART_ITEMS = 10;
|
||||||
|
MUTATION_ORDER_COMPLETED = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MutationRequest {
|
||||||
|
string cart_id = 1;
|
||||||
|
MutationType type = 2;
|
||||||
|
bytes payload = 3; // Serialized specific mutation proto
|
||||||
|
int64 client_timestamp = 4; // Optional; server fills if zero
|
||||||
|
}
|
||||||
|
|
||||||
|
message MutationReply {
|
||||||
|
int32 status_code = 1;
|
||||||
|
bytes payload = 2; // JSON cart state or error string
|
||||||
|
}
|
||||||
|
|
||||||
|
message StateRequest {
|
||||||
|
string cart_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StateReply {
|
||||||
|
int32 status_code = 1;
|
||||||
|
bytes payload = 2; // JSON cart state
|
||||||
|
}
|
||||||
|
|
||||||
|
service CartActor {
|
||||||
|
rpc Mutate(MutationRequest) returns (MutationReply);
|
||||||
|
rpc GetState(StateRequest) returns (StateReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
### 5.2 Control Plane Proto
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
package control;
|
||||||
|
option go_package = "git.tornberg.me/go-cart-actor/proto;proto";
|
||||||
|
|
||||||
|
message Empty {}
|
||||||
|
|
||||||
|
message PingReply {
|
||||||
|
string host = 1;
|
||||||
|
int64 unix_time = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NegotiateRequest {
|
||||||
|
repeated string known_hosts = 1;
|
||||||
|
}
|
||||||
|
message NegotiateReply {
|
||||||
|
repeated string hosts = 1; // Healthy hosts returned
|
||||||
|
}
|
||||||
|
|
||||||
|
message CartIdsReply {
|
||||||
|
repeated string cart_ids = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OwnerChangeRequest {
|
||||||
|
string cart_id = 1;
|
||||||
|
string new_host = 2;
|
||||||
|
}
|
||||||
|
message OwnerChangeAck {
|
||||||
|
bool accepted = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClosingNotice {
|
||||||
|
string host = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service ControlPlane {
|
||||||
|
rpc Ping(Empty) returns (PingReply);
|
||||||
|
rpc Negotiate(NegotiateRequest) returns (NegotiateReply);
|
||||||
|
rpc GetCartIds(Empty) returns (CartIdsReply);
|
||||||
|
rpc ConfirmOwner(OwnerChangeRequest) returns (OwnerChangeAck);
|
||||||
|
rpc Closing(ClosingNotice) returns (OwnerChangeAck);
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Message Type Mapping
|
||||||
|
|
||||||
|
| Legacy Constant | Numeric | New Enum Value |
|
||||||
|
|-----------------|---------|-----------------------------|
|
||||||
|
| AddRequestType | 1 | MUTATION_ADD_REQUEST |
|
||||||
|
| AddItemType | 2 | MUTATION_ADD_ITEM |
|
||||||
|
| RemoveItemType | 4 | MUTATION_REMOVE_ITEM |
|
||||||
|
| RemoveDeliveryType | 5 | MUTATION_REMOVE_DELIVERY |
|
||||||
|
| ChangeQuantityType | 6 | MUTATION_CHANGE_QUANTITY |
|
||||||
|
| SetDeliveryType | 7 | MUTATION_SET_DELIVERY |
|
||||||
|
| SetPickupPointType | 8 | MUTATION_SET_PICKUP_POINT |
|
||||||
|
| CreateCheckoutOrderType | 9 | MUTATION_CREATE_CHECKOUT_ORDER |
|
||||||
|
| SetCartItemsType | 10 | MUTATION_SET_CART_ITEMS |
|
||||||
|
| OrderCompletedType | 11 | MUTATION_ORDER_COMPLETED |
|
||||||
|
|
||||||
|
Persisted events keep original numeric codes; reconstruction simply casts to `MutationType`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Components To Remove / Replace
|
||||||
|
|
||||||
|
Remove (after migration complete):
|
||||||
|
- `remote-grain.go`
|
||||||
|
- `rpc-server.go`
|
||||||
|
- Any packet/frame-specific types solely used by the above (search: `FrameWithPayload`, `RemoteHandleMutation`, `RemoteGetState` where not reused by disk or internal logic).
|
||||||
|
- The constants representing network frame types in `synced-pool.go` (RemoteNegotiate, AckChange, etc.) replaced by gRPC calls.
|
||||||
|
- netpool usage for remote cart channel (control plane also no longer needs `Connection` abstraction).
|
||||||
|
|
||||||
|
Retain (until reworked or optionally cleaned later):
|
||||||
|
- `message.go` (for persistence)
|
||||||
|
- `message-handler.go`
|
||||||
|
- `cart-grain.go`
|
||||||
|
- `messages.proto` (underlying mutation messages)
|
||||||
|
- HTTP API server and REST handlers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. New / Modified Components
|
||||||
|
|
||||||
|
New files (planned):
|
||||||
|
- `proto/cart_actor.proto`
|
||||||
|
- `proto/control_plane.proto`
|
||||||
|
- `grpc/cart_actor_server.go` (server impl)
|
||||||
|
- `grpc/cart_actor_client.go` (client adapter implementing `Grain`)
|
||||||
|
- `grpc/control_plane_server.go`
|
||||||
|
- `grpc/control_plane_client.go`
|
||||||
|
- `grpc/interceptors.go` (metrics, logging, optional tracing hooks)
|
||||||
|
- `remote_grain_grpc.go` (adapter bridging existing interfaces)
|
||||||
|
- `control_plane_adapter.go` (replaces frame handlers in `SyncedPool`)
|
||||||
|
|
||||||
|
Modified:
|
||||||
|
- `synced-pool.go` (remote host management now uses gRPC clients; negotiation logic updated)
|
||||||
|
- `main.go` (initialize both gRPC services on startup)
|
||||||
|
- `go.mod` (add `google.golang.org/grpc`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Step-by-Step Migration Plan
|
||||||
|
|
||||||
|
1. Add proto files and generate Go code (`protoc --go_out --go-grpc_out`).
|
||||||
|
2. Implement `CartActorServer`:
|
||||||
|
- Translate `MutationRequest` to `Message`.
|
||||||
|
- Use existing handler registry for payload encode/decode.
|
||||||
|
- Return JSON cart state.
|
||||||
|
3. Implement `CartActorClient` wrapper (`RemoteGrainGRPC`) implementing:
|
||||||
|
- `HandleMessage`: Build envelope, call `Mutate`.
|
||||||
|
- `GetCurrentState`: Call `GetState`.
|
||||||
|
4. Implement `ControlPlaneServer` with methods:
|
||||||
|
- `Ping`: returns host + time.
|
||||||
|
- `Negotiate`: merge host lists; emulate old logic.
|
||||||
|
- `GetCartIds`: iterate local grains.
|
||||||
|
- `ConfirmOwner`: replicate quorum flow (accept always; error path for future).
|
||||||
|
- `Closing`: schedule remote removal.
|
||||||
|
5. Implement `ControlPlaneClient` used inside `SyncedPool.AddRemote`.
|
||||||
|
6. Refactor `SyncedPool`:
|
||||||
|
- Replace frame handlers registration with gRPC client calls.
|
||||||
|
- Replace `Server.AddHandler(...)` start-up with launching gRPC server.
|
||||||
|
- Implement periodic health checks using `Ping`.
|
||||||
|
7. Remove old connection constructs for 1337/1338.
|
||||||
|
8. Metrics:
|
||||||
|
- Add unary interceptor capturing duration and status.
|
||||||
|
- Replace packet counters with `cart_grpc_mutate_calls_total`, `cart_grpc_control_calls_total`, histograms for latency.
|
||||||
|
9. Update `main.go` to start:
|
||||||
|
- gRPC server(s).
|
||||||
|
- HTTP server as before.
|
||||||
|
10. Delete legacy files & update README build instructions.
|
||||||
|
11. Load testing & profiling on Raspberry Pi hardware (or ARM emulation).
|
||||||
|
12. Final cleanup & dead code removal (search for now-unused constants & structs).
|
||||||
|
13. Tag release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Performance Considerations (Raspberry Pi Focus)
|
||||||
|
|
||||||
|
- Single `*grpc.ClientConn` per remote host (HTTP/2 multiplexing) to reduce file descriptor and handshake overhead.
|
||||||
|
- Use small keepalive pings (optional) only if connections drop; default may suffice.
|
||||||
|
- Avoid reflection / dynamic dispatch in hot path: pre-build a mapping from `MutationType` to handler function.
|
||||||
|
- Reuse byte buffers:
|
||||||
|
- Implement a `sync.Pool` for mutation serialization to reduce GC pressure.
|
||||||
|
- Enforce per-RPC deadlines (e.g. 300–400ms) to avoid pile-ups.
|
||||||
|
- Backpressure:
|
||||||
|
- Before dispatch: if local grain pool at capacity and target grain is remote, abort early with 503 to caller (optional).
|
||||||
|
- Disable gRPC compression for small payloads (mutation messages are small). Condition compression if payload > threshold (e.g. 8KB).
|
||||||
|
- Compile with `-ldflags="-s -w"` in production to reduce binary size (optional).
|
||||||
|
- Enable `GOMAXPROCS` tuned to CPU cores; Pi often benefits from leaving default but monitor.
|
||||||
|
- Use histograms with limited buckets to reduce Prometheus cardinality.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Testing Strategy
|
||||||
|
|
||||||
|
Unit:
|
||||||
|
- Message type mapping tests (legacy -> enum).
|
||||||
|
- Envelope roundtrip: Original proto -> payload -> gRPC -> server decode -> internal Message.
|
||||||
|
|
||||||
|
Integration:
|
||||||
|
- Two-node cluster simulation:
|
||||||
|
- Mutate cart on Node A, ownership moves, verify remote access from Node B.
|
||||||
|
- Quorum failure simulation (temporarily reject `ConfirmOwner`).
|
||||||
|
- Control plane negotiation: start nodes in staggered order, assert final membership.
|
||||||
|
|
||||||
|
Load/Perf:
|
||||||
|
- Benchmark local mutation vs remote mutation latency.
|
||||||
|
- High concurrency test (N goroutines each performing X mutations).
|
||||||
|
- Memory profiling (ensure no large buffer retention).
|
||||||
|
|
||||||
|
Failure Injection:
|
||||||
|
- Kill a node mid-mutation; client call should timeout and not corrupt local state.
|
||||||
|
- Simulated network partition: drop `Ping` replies; ensure host removal path triggers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Rollback Strategy
|
||||||
|
|
||||||
|
Because no mixed-version compatibility is provided, rollback = redeploy previous version containing legacy protocol:
|
||||||
|
1. Stop all new-version pods.
|
||||||
|
2. Deploy old version cluster-wide.
|
||||||
|
3. No data migration needed (event persistence unaffected).
|
||||||
|
|
||||||
|
Note: Avoid partial upgrades; perform full rolling restart quickly to prevent split-brain (new nodes won’t talk to old nodes).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Risks & Mitigations
|
||||||
|
|
||||||
|
| Risk | Description | Mitigation |
|
||||||
|
|------|-------------|------------|
|
||||||
|
| Full-cluster restart required | No mixed compatibility | Schedule maintenance window |
|
||||||
|
| gRPC adds CPU overhead | Envelope + marshaling cost | Buffer reuse, keep small messages uncompressed |
|
||||||
|
| Ownership race | Timing differences after refactor | Add explicit logs + tests around `RequestOwnership` path |
|
||||||
|
| Hidden dependency on frame-level status codes | Some code may assume `FrameWithPayload` fields | Wrap gRPC responses into minimal compatibility structs until fully removed |
|
||||||
|
| Memory growth | Connection reuse & pooled buffers not implemented initially | Add `sync.Pool` & track memory via pprof early |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Logging & Observability
|
||||||
|
|
||||||
|
- Structured log entries for:
|
||||||
|
- Ownership changes
|
||||||
|
- Negotiation rounds
|
||||||
|
- Remote spawn events
|
||||||
|
- Mutation failures (with cart id, mutation type)
|
||||||
|
- Metrics:
|
||||||
|
- `cart_grpc_mutate_duration_seconds` (histogram)
|
||||||
|
- `cart_grpc_mutate_errors_total`
|
||||||
|
- `cart_grpc_control_duration_seconds`
|
||||||
|
- `cart_remote_hosts` (gauge)
|
||||||
|
- Retain existing grain counts.
|
||||||
|
- Optional future: OpenTelemetry tracing (span per remote mutation).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Future Enhancements (Post-Migration)
|
||||||
|
|
||||||
|
- Replace JSON state with `CartState` proto and provide streaming watch API.
|
||||||
|
- mTLS between nodes (certificate rotation via K8s Secret or SPIRE).
|
||||||
|
- Distributed tracing integration.
|
||||||
|
- Ownership leasing with TTL and optimistic renewal.
|
||||||
|
- Delta replication or CRDT-based conflict resolution for experimentation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Task Breakdown & Estimates
|
||||||
|
|
||||||
|
| Task | Estimate |
|
||||||
|
|------|----------|
|
||||||
|
| Proto definitions & generation | 0.5d |
|
||||||
|
| CartActor server/client | 1.0d |
|
||||||
|
| ControlPlane server/client | 1.0d |
|
||||||
|
| SyncedPool refactor | 1.0d |
|
||||||
|
| Metrics & interceptors | 0.5d |
|
||||||
|
| Remove legacy code & cleanup | 0.5d |
|
||||||
|
| Tests (unit + integration) | 1.5d |
|
||||||
|
| Benchmark & tuning | 0.5–1.0d |
|
||||||
|
| Total | ~6–7d |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. Open Questions (Confirm Before Implementation)
|
||||||
|
|
||||||
|
1. Combine both services on a single port (simplify ops) or keep dual-port first? (Default here: keep dual, but easy to merge.)
|
||||||
|
2. Minimum Go version remains 1.24.x—acceptable to add `google.golang.org/grpc` latest?
|
||||||
|
3. Accept adding `sync.Pool` micro-optimizations in first pass or postpone?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. Acceptance Criteria
|
||||||
|
|
||||||
|
- All previous integration tests (adjusted to gRPC) pass.
|
||||||
|
- Cart operations (add, remove, delivery, checkout) function across at least a 2‑node cluster.
|
||||||
|
- Control plane negotiation forms consistent host list.
|
||||||
|
- Latency for a remote mutation does not degrade beyond an acceptable threshold (define baseline before merge).
|
||||||
|
- Legacy networking code fully removed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. Next Steps (If Approved)
|
||||||
|
|
||||||
|
1. Implement proto files and commit.
|
||||||
|
2. Scaffold server & client code.
|
||||||
|
3. Refactor `SyncedPool` and `main.go`.
|
||||||
|
4. Add metrics and tests.
|
||||||
|
5. Run benchmark on target Pi hardware.
|
||||||
|
6. Review & merge.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
End of Plan.
|
||||||
179
README.md
179
README.md
@@ -1 +1,178 @@
|
|||||||
protoc --proto_path=proto --go_out=out --go_opt=paths=source_relative proto/messages.proto
|
# Go Cart Actor
|
||||||
|
|
||||||
|
A distributed cart management system using the actor model pattern.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Go 1.24.2+
|
||||||
|
- Protocol Buffers compiler (`protoc`)
|
||||||
|
- protoc-gen-go and protoc-gen-go-grpc plugins
|
||||||
|
|
||||||
|
### Installing Protocol Buffers
|
||||||
|
|
||||||
|
On Windows:
|
||||||
|
```powershell
|
||||||
|
winget install protobuf
|
||||||
|
```
|
||||||
|
|
||||||
|
On macOS:
|
||||||
|
```bash
|
||||||
|
brew install protobuf
|
||||||
|
```
|
||||||
|
|
||||||
|
On Linux:
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt install protobuf-compiler
|
||||||
|
|
||||||
|
# Or download from: https://github.com/protocolbuffers/protobuf/releases
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installing Go protobuf plugin
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Working with Protocol Buffers
|
||||||
|
|
||||||
|
### Generating Go code from proto files
|
||||||
|
|
||||||
|
After modifying any proto (`proto/messages.proto`, `proto/cart_actor.proto`, `proto/control_plane.proto`), regenerate the Go code (all three share the unified `messages` package):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd proto
|
||||||
|
protoc --go_out=. --go_opt=paths=source_relative \
|
||||||
|
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||||
|
messages.proto cart_actor.proto control_plane.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protocol Buffer Messages
|
||||||
|
|
||||||
|
The `proto/messages.proto` file defines the following message types:
|
||||||
|
|
||||||
|
- `AddRequest` - Add items to cart (includes quantity, sku, country, optional storeId)
|
||||||
|
- `SetCartRequest` - Set entire cart contents
|
||||||
|
- `AddItem` - Complete item information for cart
|
||||||
|
- `RemoveItem` - Remove item from cart
|
||||||
|
- `ChangeQuantity` - Update item quantity
|
||||||
|
- `SetDelivery` - Configure delivery options
|
||||||
|
- `SetPickupPoint` - Set pickup location
|
||||||
|
- `PickupPoint` - Pickup point details
|
||||||
|
- `RemoveDelivery` - Remove delivery option
|
||||||
|
- `CreateCheckoutOrder` - Initiate checkout
|
||||||
|
- `OrderCreated` - Order creation response
|
||||||
|
|
||||||
|
### Building the project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTTP API Quick Start (curl Examples)
|
||||||
|
|
||||||
|
Assuming the service is reachable at http://localhost:8080 and the cart API is mounted at /cart.
|
||||||
|
Most endpoints use an HTTP cookie named `cartid` to track the cart. The first request will set it.
|
||||||
|
|
||||||
|
### 1. Get (or create) a cart
|
||||||
|
```bash
|
||||||
|
curl -i http://localhost:8080/cart/
|
||||||
|
```
|
||||||
|
Response sets a `cartid` cookie and returns the current (possibly empty) cart JSON.
|
||||||
|
|
||||||
|
### 2. Add an item by SKU (implicit quantity = 1)
|
||||||
|
```bash
|
||||||
|
curl -i --cookie-jar cookies.txt http://localhost:8080/cart/add/TEST-SKU-123
|
||||||
|
```
|
||||||
|
Stores cookie in `cookies.txt` for subsequent calls.
|
||||||
|
|
||||||
|
### 3. Add an item with explicit payload (country, quantity)
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sku":"TEST-SKU-456","quantity":2,"country":"se"}' \
|
||||||
|
http://localhost:8080/cart/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Change quantity of an existing line
|
||||||
|
(First list the cart to find `id` of the line; here we use id=1 as an example)
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt \
|
||||||
|
-X PUT -H "Content-Type: application/json" \
|
||||||
|
-d '{"id":1,"quantity":3}' \
|
||||||
|
http://localhost:8080/cart/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Remove an item
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt -X DELETE http://localhost:8080/cart/1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Set entire cart contents (overwrites items)
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"items":[{"sku":"TEST-SKU-AAA","quantity":1,"country":"se"},{"sku":"TEST-SKU-BBB","quantity":2,"country":"se"}]}' \
|
||||||
|
http://localhost:8080/cart/set
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Add a delivery (provider + optional items)
|
||||||
|
If `items` is empty or omitted, all items without a delivery get this one.
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"provider":"standard","items":[1,2]}' \
|
||||||
|
http://localhost:8080/cart/delivery
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Remove a delivery by deliveryId
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt -X DELETE http://localhost:8080/cart/delivery/1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Set a pickup point for a delivery
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt \
|
||||||
|
-X PUT -H "Content-Type: application/json" \
|
||||||
|
-d '{"id":"PUP123","name":"Locker 5","address":"Main St 1","city":"Stockholm","zip":"11122","country":"SE"}' \
|
||||||
|
http://localhost:8080/cart/delivery/1/pickupPoint
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. Checkout (returns HTML snippet from Klarna)
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt http://localhost:8080/cart/checkout
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11. Using a known cart id directly (bypassing cookie)
|
||||||
|
If you already have a cart id (e.g. 1720000000000000):
|
||||||
|
```bash
|
||||||
|
CART_ID=1720000000000000
|
||||||
|
curl -i http://localhost:8080/cart/byid/$CART_ID
|
||||||
|
curl -i -X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"sku":"TEST-SKU-XYZ","quantity":1,"country":"se"}' \
|
||||||
|
http://localhost:8080/cart/byid/$CART_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### 12. Clear cart cookie (forces a new cart on next request)
|
||||||
|
```bash
|
||||||
|
curl -i --cookie cookies.txt -X DELETE http://localhost:8080/cart/
|
||||||
|
```
|
||||||
|
|
||||||
|
Tip: Use `--cookie-jar` and `--cookie` to persist the session across multiple commands:
|
||||||
|
```bash
|
||||||
|
curl --cookie-jar cookies.txt http://localhost:8080/cart/
|
||||||
|
curl --cookie cookies.txt http://localhost:8080/cart/add/TEST-SKU-123
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- Always regenerate protobuf Go code after modifying any `.proto` files (messages/cart_actor/control_plane)
|
||||||
|
- The generated `messages.pb.go` file should not be edited manually
|
||||||
|
- Make sure your PATH includes the protoc-gen-go binary location (usually `$GOPATH/bin`)
|
||||||
83
amqp-order-handler.go
Normal file
83
amqp-order-handler.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
amqp "github.com/rabbitmq/amqp091-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AmqpOrderHandler struct {
|
||||||
|
Url string
|
||||||
|
connection *amqp.Connection
|
||||||
|
//channel *amqp.Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
topic = "order-placed"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *AmqpOrderHandler) Connect() error {
|
||||||
|
|
||||||
|
conn, err := amqp.DialConfig(t.Url, amqp.Config{
|
||||||
|
//Vhost: "/",
|
||||||
|
Properties: amqp.NewConnectionProperties(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.connection = conn
|
||||||
|
ch, err := conn.Channel()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ch.Close()
|
||||||
|
if err := ch.ExchangeDeclare(
|
||||||
|
topic, // name
|
||||||
|
"topic", // type
|
||||||
|
true, // durable
|
||||||
|
false, // auto-delete
|
||||||
|
false, // internal
|
||||||
|
false, // noWait
|
||||||
|
nil, // arguments
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = ch.QueueDeclare(
|
||||||
|
topic, // name of the queue
|
||||||
|
true, // durable
|
||||||
|
false, // delete when unused
|
||||||
|
false, // exclusive
|
||||||
|
false, // noWait
|
||||||
|
nil, // arguments
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AmqpOrderHandler) Close() error {
|
||||||
|
log.Println("Closing master channel")
|
||||||
|
return t.connection.Close()
|
||||||
|
//return t.channel.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *AmqpOrderHandler) OrderCompleted(data []byte) error {
|
||||||
|
ch, err := t.connection.Channel()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ch.Close()
|
||||||
|
return ch.Publish(
|
||||||
|
topic,
|
||||||
|
topic,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
amqp.Publishing{
|
||||||
|
ContentType: "application/json",
|
||||||
|
Body: data,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ Content-Type: application/json
|
|||||||
}
|
}
|
||||||
|
|
||||||
### Delete item from cart
|
### Delete item from cart
|
||||||
DELETE https://cart.tornberg.me/api/12345/1
|
DELETE https://cart.tornberg.me/api/1002/1
|
||||||
|
|
||||||
|
|
||||||
### Set delivery
|
### Set delivery
|
||||||
|
|||||||
290
cart-grain.go
290
cart-grain.go
@@ -1,18 +1,35 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
messages "git.tornberg.me/go-cart-actor/proto"
|
messages "git.tornberg.me/go-cart-actor/proto"
|
||||||
klarna "github.com/Flaconi/go-klarna"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CartId [16]byte
|
type CartId [16]byte
|
||||||
|
|
||||||
|
// String returns the cart id as a trimmed UTF-8 string (trailing zero bytes removed).
|
||||||
|
func (id CartId) String() string {
|
||||||
|
n := 0
|
||||||
|
for n < len(id) && id[n] != 0 {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return string(id[:n])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToCartId converts an arbitrary string to a fixed-size CartId (truncating or padding with zeros).
|
||||||
|
func ToCartId(s string) CartId {
|
||||||
|
var id CartId
|
||||||
|
copy(id[:], []byte(s))
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
func (id CartId) MarshalJSON() ([]byte, error) {
|
func (id CartId) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(id.String())
|
return json.Marshal(id.String())
|
||||||
}
|
}
|
||||||
@@ -37,18 +54,31 @@ const (
|
|||||||
|
|
||||||
type CartItem struct {
|
type CartItem struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
|
ItemId int `json:"itemId,omitempty"`
|
||||||
ParentId int `json:"parentId,omitempty"`
|
ParentId int `json:"parentId,omitempty"`
|
||||||
Sku string `json:"sku"`
|
Sku string `json:"sku"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Price int64 `json:"price"`
|
Price int64 `json:"price"`
|
||||||
|
TotalPrice int64 `json:"totalPrice"`
|
||||||
|
TotalTax int64 `json:"totalTax"`
|
||||||
OrgPrice int64 `json:"orgPrice"`
|
OrgPrice int64 `json:"orgPrice"`
|
||||||
Stock StockStatus `json:"stock"`
|
Stock StockStatus `json:"stock"`
|
||||||
Quantity int `json:"qty"`
|
Quantity int `json:"qty"`
|
||||||
Tax int `json:"tax"`
|
Tax int `json:"tax"`
|
||||||
|
TaxRate int `json:"taxRate"`
|
||||||
|
Brand string `json:"brand,omitempty"`
|
||||||
|
Category string `json:"category,omitempty"`
|
||||||
|
Category2 string `json:"category2,omitempty"`
|
||||||
|
Category3 string `json:"category3,omitempty"`
|
||||||
|
Category4 string `json:"category4,omitempty"`
|
||||||
|
Category5 string `json:"category5,omitempty"`
|
||||||
Disclaimer string `json:"disclaimer,omitempty"`
|
Disclaimer string `json:"disclaimer,omitempty"`
|
||||||
|
SellerId string `json:"sellerId,omitempty"`
|
||||||
|
SellerName string `json:"sellerName,omitempty"`
|
||||||
ArticleType string `json:"type,omitempty"`
|
ArticleType string `json:"type,omitempty"`
|
||||||
Image string `json:"image,omitempty"`
|
Image string `json:"image,omitempty"`
|
||||||
Outlet *string `json:"outlet,omitempty"`
|
Outlet *string `json:"outlet,omitempty"`
|
||||||
|
StoreId *string `json:"storeId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CartDelivery struct {
|
type CartDelivery struct {
|
||||||
@@ -60,15 +90,20 @@ type CartDelivery struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CartGrain struct {
|
type CartGrain struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
lastItemId int
|
lastItemId int
|
||||||
lastDeliveryId int
|
lastDeliveryId int
|
||||||
storageMessages []Message
|
storageMessages []Message
|
||||||
Id CartId `json:"id"`
|
Id CartId `json:"id"`
|
||||||
Items []*CartItem `json:"items"`
|
Items []*CartItem `json:"items"`
|
||||||
TotalPrice int64 `json:"totalPrice"`
|
TotalPrice int64 `json:"totalPrice"`
|
||||||
Deliveries []*CartDelivery `json:"deliveries,omitempty"`
|
TotalTax int64 `json:"totalTax"`
|
||||||
Processing bool `json:"processing"`
|
TotalDiscount int64 `json:"totalDiscount"`
|
||||||
|
Deliveries []*CartDelivery `json:"deliveries,omitempty"`
|
||||||
|
Processing bool `json:"processing"`
|
||||||
|
PaymentInProgress bool `json:"paymentInProgress"`
|
||||||
|
OrderReference string `json:"orderReference,omitempty"`
|
||||||
|
PaymentStatus string `json:"paymentStatus,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Grain interface {
|
type Grain interface {
|
||||||
@@ -98,44 +133,51 @@ func (c *CartGrain) GetCurrentState() (*FrameWithPayload, error) {
|
|||||||
return &ret, nil
|
return &ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInt(data interface{}) (int, error) {
|
func getInt(data float64, ok bool) (int, error) {
|
||||||
switch v := data.(type) {
|
if !ok {
|
||||||
case float64:
|
|
||||||
return int(v), nil
|
|
||||||
case int:
|
|
||||||
return v, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("invalid type")
|
return 0, fmt.Errorf("invalid type")
|
||||||
}
|
}
|
||||||
|
return int(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getItemData(sku string, qty int) (*messages.AddItem, error) {
|
func getItemData(sku string, qty int, country string) (*messages.AddItem, error) {
|
||||||
item, err := FetchItem(sku)
|
item, err := FetchItem(sku, country)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
orgPrice, _ := getInt(item.Fields[5])
|
orgPrice, _ := getInt(item.GetNumberFieldValue(5)) // getInt(item.Fields[5])
|
||||||
|
|
||||||
price, priceErr := getInt(item.Fields[4])
|
price, priceErr := getInt(item.GetNumberFieldValue(4)) //Fields[4]
|
||||||
|
|
||||||
if priceErr != nil {
|
if priceErr != nil {
|
||||||
return nil, fmt.Errorf("invalid price")
|
return nil, fmt.Errorf("invalid price")
|
||||||
}
|
}
|
||||||
|
|
||||||
stock := InStock
|
stock := InStock
|
||||||
|
/*item.t
|
||||||
if item.StockLevel == "0" || item.StockLevel == "" {
|
if item.StockLevel == "0" || item.StockLevel == "" {
|
||||||
stock = OutOfStock
|
stock = OutOfStock
|
||||||
} else if item.StockLevel == "5+" {
|
} else if item.StockLevel == "5+" {
|
||||||
stock = LowStock
|
stock = LowStock
|
||||||
}
|
}*/
|
||||||
articleType, _ := item.Fields[1].(string)
|
articleType, _ := item.GetStringFieldValue(1) //.Fields[1].(string)
|
||||||
outletGrade, ok := item.Fields[20].(string)
|
outletGrade, ok := item.GetStringFieldValue(20) //.Fields[20].(string)
|
||||||
var outlet *string
|
var outlet *string
|
||||||
if ok {
|
if ok {
|
||||||
outlet = &outletGrade
|
outlet = &outletGrade
|
||||||
}
|
}
|
||||||
|
sellerId, _ := item.GetStringFieldValue(24) // .Fields[24].(string)
|
||||||
|
sellerName, _ := item.GetStringFieldValue(9) // .Fields[9].(string)
|
||||||
|
|
||||||
|
brand, _ := item.GetStringFieldValue(2) //.Fields[2].(string)
|
||||||
|
category, _ := item.GetStringFieldValue(10) //.Fields[10].(string)
|
||||||
|
category2, _ := item.GetStringFieldValue(11) //.Fields[11].(string)
|
||||||
|
category3, _ := item.GetStringFieldValue(12) //.Fields[12].(string)
|
||||||
|
category4, _ := item.GetStringFieldValue(13) //Fields[13].(string)
|
||||||
|
category5, _ := item.GetStringFieldValue(14) //.Fields[14].(string)
|
||||||
|
|
||||||
return &messages.AddItem{
|
return &messages.AddItem{
|
||||||
|
ItemId: int64(item.Id),
|
||||||
Quantity: int32(qty),
|
Quantity: int32(qty),
|
||||||
Price: int64(price),
|
Price: int64(price),
|
||||||
OrgPrice: int64(orgPrice),
|
OrgPrice: int64(orgPrice),
|
||||||
@@ -143,18 +185,28 @@ func getItemData(sku string, qty int) (*messages.AddItem, error) {
|
|||||||
Name: item.Title,
|
Name: item.Title,
|
||||||
Image: item.Img,
|
Image: item.Img,
|
||||||
Stock: int32(stock),
|
Stock: int32(stock),
|
||||||
|
Brand: brand,
|
||||||
|
Category: category,
|
||||||
|
Category2: category2,
|
||||||
|
Category3: category3,
|
||||||
|
Category4: category4,
|
||||||
|
Category5: category5,
|
||||||
Tax: 2500,
|
Tax: 2500,
|
||||||
|
SellerId: sellerId,
|
||||||
|
SellerName: sellerName,
|
||||||
ArticleType: articleType,
|
ArticleType: articleType,
|
||||||
Disclaimer: item.Disclaimer,
|
Disclaimer: item.Disclaimer,
|
||||||
|
Country: country,
|
||||||
Outlet: outlet,
|
Outlet: outlet,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CartGrain) AddItem(sku string, qty int) (*FrameWithPayload, error) {
|
func (c *CartGrain) AddItem(sku string, qty int, country string, storeId *string) (*FrameWithPayload, error) {
|
||||||
cartItem, err := getItemData(sku, qty)
|
cartItem, err := getItemData(sku, qty, country)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
cartItem.StoreId = storeId
|
||||||
return c.HandleMessage(&Message{
|
return c.HandleMessage(&Message{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
Content: cartItem,
|
Content: cartItem,
|
||||||
@@ -222,9 +274,9 @@ func (c *CartGrain) FindItemWithSku(sku string) (*CartItem, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTaxAmount(total int, tax int) int {
|
func GetTaxAmount(total int64, tax int) int64 {
|
||||||
taxD := 10000 / float64(tax)
|
taxD := 10000 / float64(tax)
|
||||||
return int(float64(total) / float64((1 + taxD)))
|
return int64(float64(total) / float64((1 + taxD)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPayload, error) {
|
func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPayload, error) {
|
||||||
@@ -235,18 +287,34 @@ func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPa
|
|||||||
grainMutations.Inc()
|
grainMutations.Inc()
|
||||||
var err error
|
var err error
|
||||||
switch message.Type {
|
switch message.Type {
|
||||||
|
case SetCartItemsType:
|
||||||
|
msg, ok := message.Content.(*messages.SetCartRequest)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("expected SetCartItems")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
c.Items = make([]*CartItem, 0, len(msg.Items))
|
||||||
|
c.mu.Unlock()
|
||||||
|
for _, item := range msg.Items {
|
||||||
|
c.AddItem(item.Sku, int(item.Quantity), item.Country, item.StoreId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
case AddRequestType:
|
case AddRequestType:
|
||||||
msg, ok := message.Content.(*messages.AddRequest)
|
msg, ok := message.Content.(*messages.AddRequest)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fmt.Errorf("expected AddRequest")
|
err = fmt.Errorf("expected AddRequest")
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
existingItem, found := c.FindItemWithSku(msg.Sku)
|
existingItem, found := c.FindItemWithSku(msg.Sku)
|
||||||
if found {
|
if found {
|
||||||
existingItem.Quantity += int(msg.Quantity)
|
existingItem.Quantity += int(msg.Quantity)
|
||||||
c.TotalPrice += existingItem.Price * int64(msg.Quantity)
|
c.UpdateTotals()
|
||||||
} else {
|
} else {
|
||||||
return c.AddItem(msg.Sku, int(msg.Quantity)) // extent AddRequest to include quantity
|
return c.AddItem(msg.Sku, int(msg.Quantity), msg.Country, msg.StoreId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
case AddItemType:
|
case AddItemType:
|
||||||
msg, ok := message.Content.(*messages.AddItem)
|
msg, ok := message.Content.(*messages.AddItem)
|
||||||
@@ -260,7 +328,7 @@ func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPa
|
|||||||
existingItem, found := c.FindItemWithSku(msg.Sku)
|
existingItem, found := c.FindItemWithSku(msg.Sku)
|
||||||
if found {
|
if found {
|
||||||
existingItem.Quantity += int(msg.Quantity)
|
existingItem.Quantity += int(msg.Quantity)
|
||||||
c.TotalPrice += existingItem.Price * int64(msg.Quantity)
|
c.UpdateTotals()
|
||||||
} else {
|
} else {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
c.lastItemId++
|
c.lastItemId++
|
||||||
@@ -268,62 +336,87 @@ func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPa
|
|||||||
if msg.Tax > 0 {
|
if msg.Tax > 0 {
|
||||||
tax = int(msg.Tax)
|
tax = int(msg.Tax)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
taxAmount := GetTaxAmount(msg.Price, tax)
|
||||||
|
|
||||||
c.Items = append(c.Items, &CartItem{
|
c.Items = append(c.Items, &CartItem{
|
||||||
Id: c.lastItemId,
|
Id: c.lastItemId,
|
||||||
|
ItemId: int(msg.ItemId),
|
||||||
Quantity: int(msg.Quantity),
|
Quantity: int(msg.Quantity),
|
||||||
Sku: msg.Sku,
|
Sku: msg.Sku,
|
||||||
Name: msg.Name,
|
Name: msg.Name,
|
||||||
Price: msg.Price,
|
Price: msg.Price,
|
||||||
|
TotalPrice: msg.Price * int64(msg.Quantity),
|
||||||
|
TotalTax: int64(taxAmount * int64(msg.Quantity)),
|
||||||
Image: msg.Image,
|
Image: msg.Image,
|
||||||
Stock: StockStatus(msg.Stock),
|
Stock: StockStatus(msg.Stock),
|
||||||
Disclaimer: msg.Disclaimer,
|
Disclaimer: msg.Disclaimer,
|
||||||
|
Brand: msg.Brand,
|
||||||
|
Category: msg.Category,
|
||||||
|
Category2: msg.Category2,
|
||||||
|
Category3: msg.Category3,
|
||||||
|
Category4: msg.Category4,
|
||||||
|
Category5: msg.Category5,
|
||||||
OrgPrice: msg.OrgPrice,
|
OrgPrice: msg.OrgPrice,
|
||||||
ArticleType: msg.ArticleType,
|
ArticleType: msg.ArticleType,
|
||||||
Outlet: msg.Outlet,
|
Outlet: msg.Outlet,
|
||||||
Tax: tax,
|
SellerId: msg.SellerId,
|
||||||
|
SellerName: msg.SellerName,
|
||||||
|
Tax: int(taxAmount),
|
||||||
|
TaxRate: tax,
|
||||||
|
StoreId: msg.StoreId,
|
||||||
})
|
})
|
||||||
c.TotalPrice += msg.Price * int64(msg.Quantity)
|
c.UpdateTotals()
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
case ChangeQuantityType:
|
case ChangeQuantityType:
|
||||||
msg, ok := message.Content.(*messages.ChangeQuantity)
|
msg, ok := message.Content.(*messages.ChangeQuantity)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fmt.Errorf("expected RemoveItem")
|
err = fmt.Errorf("expected ChangeQuantity")
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
for i, item := range c.Items {
|
for i, item := range c.Items {
|
||||||
if item.Id == int(msg.Id) {
|
if item.Id == int(msg.Id) {
|
||||||
if item.Quantity <= int(msg.Quantity) {
|
if msg.Quantity <= 0 {
|
||||||
|
//c.TotalPrice -= item.Price * int64(item.Quantity)
|
||||||
c.Items = append(c.Items[:i], c.Items[i+1:]...)
|
c.Items = append(c.Items[:i], c.Items[i+1:]...)
|
||||||
} else {
|
} else {
|
||||||
item.Quantity -= int(msg.Quantity)
|
//diff := int(msg.Quantity) - item.Quantity
|
||||||
|
item.Quantity = int(msg.Quantity)
|
||||||
|
//c.TotalPrice += item.Price * int64(diff)
|
||||||
}
|
}
|
||||||
c.TotalPrice -= item.Price * int64(msg.Quantity)
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.UpdateTotals()
|
||||||
|
|
||||||
}
|
}
|
||||||
case RemoveItemType:
|
case RemoveItemType:
|
||||||
msg, ok := message.Content.(*messages.RemoveItem)
|
msg, ok := message.Content.(*messages.RemoveItem)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fmt.Errorf("expected RemoveItem")
|
err = fmt.Errorf("expected RemoveItem")
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
items := make([]*CartItem, 0, len(c.Items))
|
items := make([]*CartItem, 0, len(c.Items))
|
||||||
for _, item := range c.Items {
|
for _, item := range c.Items {
|
||||||
if item.Id == int(msg.Id) {
|
if item.Id == int(msg.Id) {
|
||||||
c.TotalPrice -= item.Price * int64(item.Quantity)
|
//c.TotalPrice -= item.Price * int64(item.Quantity)
|
||||||
} else {
|
} else {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Items = items
|
c.Items = items
|
||||||
|
c.UpdateTotals()
|
||||||
}
|
}
|
||||||
case SetDeliveryType:
|
case SetDeliveryType:
|
||||||
msg, ok := message.Content.(*messages.SetDelivery)
|
msg, ok := message.Content.(*messages.SetDelivery)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fmt.Errorf("expected SetDelivery")
|
err = fmt.Errorf("expected SetDelivery")
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
c.lastDeliveryId++
|
c.lastDeliveryId++
|
||||||
items := make([]int, 0)
|
items := make([]int, 0)
|
||||||
withDelivery := c.ItemsWithDelivery()
|
withDelivery := c.ItemsWithDelivery()
|
||||||
@@ -343,25 +436,25 @@ func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(items) > 0 {
|
if len(items) > 0 {
|
||||||
|
|
||||||
c.Deliveries = append(c.Deliveries, &CartDelivery{
|
c.Deliveries = append(c.Deliveries, &CartDelivery{
|
||||||
Id: c.lastDeliveryId,
|
Id: c.lastDeliveryId,
|
||||||
Provider: msg.Provider,
|
Provider: msg.Provider,
|
||||||
Price: 49,
|
PickupPoint: msg.PickupPoint,
|
||||||
Items: items,
|
Price: 4900,
|
||||||
|
Items: items,
|
||||||
})
|
})
|
||||||
c.Processing = true
|
|
||||||
go func() {
|
c.UpdateTotals()
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
c.Processing = false
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
case RemoveDeliveryType:
|
case RemoveDeliveryType:
|
||||||
msg, ok := message.Content.(*messages.RemoveDelivery)
|
msg, ok := message.Content.(*messages.RemoveDelivery)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fmt.Errorf("expected RemoveDelivery")
|
err = fmt.Errorf("expected RemoveDelivery")
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
deliveries := make([]*CartDelivery, 0, len(c.Deliveries))
|
deliveries := make([]*CartDelivery, 0, len(c.Deliveries))
|
||||||
for _, delivery := range c.Deliveries {
|
for _, delivery := range c.Deliveries {
|
||||||
if delivery.Id == int(msg.Id) {
|
if delivery.Id == int(msg.Id) {
|
||||||
@@ -371,12 +464,14 @@ func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Deliveries = deliveries
|
c.Deliveries = deliveries
|
||||||
|
c.UpdateTotals()
|
||||||
}
|
}
|
||||||
case SetPickupPointType:
|
case SetPickupPointType:
|
||||||
msg, ok := message.Content.(*messages.SetPickupPoint)
|
msg, ok := message.Content.(*messages.SetPickupPoint)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fmt.Errorf("expected SetPickupPoint")
|
err = fmt.Errorf("expected SetPickupPoint")
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
for _, delivery := range c.Deliveries {
|
for _, delivery := range c.Deliveries {
|
||||||
if delivery.Id == int(msg.DeliveryId) {
|
if delivery.Id == int(msg.DeliveryId) {
|
||||||
delivery.PickupPoint = &messages.PickupPoint{
|
delivery.PickupPoint = &messages.PickupPoint{
|
||||||
@@ -390,43 +485,61 @@ func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPa
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
case CreateCheckoutOrderType:
|
case CreateCheckoutOrderType:
|
||||||
msg, ok := message.Content.(*messages.CreateCheckoutOrder)
|
msg, ok := message.Content.(*messages.CreateCheckoutOrder)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fmt.Errorf("expected CreateCheckoutOrder")
|
err = fmt.Errorf("expected CreateCheckoutOrder")
|
||||||
} else {
|
} else {
|
||||||
orderLines := make([]*klarna.Line, 0, len(c.Items))
|
|
||||||
totalTax := 0
|
orderLines := make([]*Line, 0, len(c.Items))
|
||||||
|
|
||||||
|
c.PaymentInProgress = true
|
||||||
|
c.Processing = true
|
||||||
for _, item := range c.Items {
|
for _, item := range c.Items {
|
||||||
total := int(item.Price) * item.Quantity
|
|
||||||
taxAmount := GetTaxAmount(total, item.Tax)
|
orderLines = append(orderLines, &Line{
|
||||||
totalTax += taxAmount
|
|
||||||
orderLines = append(orderLines, &klarna.Line{
|
|
||||||
Type: "physical",
|
Type: "physical",
|
||||||
Reference: item.Sku,
|
Reference: item.Sku,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
Quantity: item.Quantity,
|
Quantity: item.Quantity,
|
||||||
UnitPrice: int(item.Price),
|
UnitPrice: int(item.Price),
|
||||||
TaxRate: int(item.Tax),
|
TaxRate: 2500, // item.TaxRate,
|
||||||
QuantityUnit: "st",
|
QuantityUnit: "st",
|
||||||
TotalAmount: total,
|
TotalAmount: int(item.TotalPrice),
|
||||||
TotalTaxAmount: taxAmount,
|
TotalTaxAmount: int(item.TotalTax),
|
||||||
ImageURL: item.Image,
|
ImageURL: fmt.Sprintf("https://www.elgiganten.se%s", item.Image),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
order := klarna.CheckoutOrder{
|
for _, line := range c.Deliveries {
|
||||||
|
if line.Price > 0 {
|
||||||
|
orderLines = append(orderLines, &Line{
|
||||||
|
Type: "shipping_fee",
|
||||||
|
Reference: line.Provider,
|
||||||
|
Name: "Delivery",
|
||||||
|
Quantity: 1,
|
||||||
|
UnitPrice: int(line.Price),
|
||||||
|
TaxRate: 2500, // item.TaxRate,
|
||||||
|
QuantityUnit: "st",
|
||||||
|
TotalAmount: int(line.Price),
|
||||||
|
TotalTaxAmount: int(GetTaxAmount(line.Price, 2500)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
order := CheckoutOrder{
|
||||||
PurchaseCountry: "SE",
|
PurchaseCountry: "SE",
|
||||||
PurchaseCurrency: "SEK",
|
PurchaseCurrency: "SEK",
|
||||||
Locale: "sv-se",
|
Locale: "sv-se",
|
||||||
OrderAmount: int(c.TotalPrice),
|
OrderAmount: int(c.TotalPrice),
|
||||||
OrderTaxAmount: totalTax,
|
OrderTaxAmount: int(c.TotalTax),
|
||||||
OrderLines: orderLines,
|
OrderLines: orderLines,
|
||||||
MerchantReference1: c.Id.String(),
|
MerchantReference1: c.Id.String(),
|
||||||
MerchantURLS: &klarna.CheckoutMerchantURLS{
|
MerchantURLS: &CheckoutMerchantURLS{
|
||||||
Terms: msg.Terms,
|
Terms: msg.Terms,
|
||||||
Checkout: msg.Checkout,
|
Checkout: msg.Checkout,
|
||||||
Confirmation: msg.Confirmation,
|
Confirmation: msg.Confirmation,
|
||||||
|
Validation: msg.Validation,
|
||||||
Push: msg.Push,
|
Push: msg.Push,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -434,9 +547,41 @@ func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result := MakeFrameWithPayload(RemoteCreateOrderReply, 200, orderPayload)
|
var klarnaOrder *CheckoutOrder
|
||||||
|
if c.OrderReference != "" {
|
||||||
|
log.Printf("Updating order id %s", c.OrderReference)
|
||||||
|
klarnaOrder, err = KlarnaInstance.UpdateOrder(c.OrderReference, bytes.NewReader(orderPayload))
|
||||||
|
} else {
|
||||||
|
klarnaOrder, err = KlarnaInstance.CreateOrder(bytes.NewReader(orderPayload))
|
||||||
|
}
|
||||||
|
// log.Printf("Order result: %+v", klarnaOrder)
|
||||||
|
if nil != err {
|
||||||
|
log.Printf("error from klarna: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c.OrderReference == "" {
|
||||||
|
c.OrderReference = klarnaOrder.ID
|
||||||
|
c.PaymentStatus = klarnaOrder.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
orderData, err := json.Marshal(klarnaOrder)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := MakeFrameWithPayload(RemoteCreateOrderReply, 200, orderData)
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
case OrderCompletedType:
|
||||||
|
msg, ok := message.Content.(*messages.OrderCreated)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("expected OrderCompleted, got %T", message.Content)
|
||||||
|
err = fmt.Errorf("expected OrderCompleted")
|
||||||
|
} else {
|
||||||
|
c.OrderReference = msg.OrderId
|
||||||
|
c.PaymentStatus = msg.Status
|
||||||
|
c.PaymentInProgress = false
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown message type %d", message.Type)
|
err = fmt.Errorf("unknown message type %d", message.Type)
|
||||||
}
|
}
|
||||||
@@ -453,3 +598,24 @@ func (c *CartGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPa
|
|||||||
msg := MakeFrameWithPayload(RemoteHandleMutationReply, 200, result)
|
msg := MakeFrameWithPayload(RemoteHandleMutationReply, 200, result)
|
||||||
return &msg, err
|
return &msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CartGrain) UpdateTotals() {
|
||||||
|
c.TotalPrice = 0
|
||||||
|
c.TotalTax = 0
|
||||||
|
c.TotalDiscount = 0
|
||||||
|
for _, item := range c.Items {
|
||||||
|
rowTotal := item.Price * int64(item.Quantity)
|
||||||
|
rowTax := int64(item.Tax) * int64(item.Quantity)
|
||||||
|
item.TotalPrice = rowTotal
|
||||||
|
item.TotalTax = rowTax
|
||||||
|
c.TotalPrice += rowTotal
|
||||||
|
c.TotalTax += rowTax
|
||||||
|
itemDiff := max(0, item.OrgPrice-item.Price)
|
||||||
|
c.TotalDiscount += itemDiff * int64(item.Quantity)
|
||||||
|
}
|
||||||
|
for _, delivery := range c.Deliveries {
|
||||||
|
c.TotalPrice += delivery.Price
|
||||||
|
c.TotalTax += GetTaxAmount(delivery.Price, 2500)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,248 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
messages "git.tornberg.me/go-cart-actor/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetMessage(t uint16, data interface{}) *Message {
|
|
||||||
ts := time.Now().Unix()
|
|
||||||
return &Message{
|
|
||||||
TimeStamp: &ts,
|
|
||||||
Type: t,
|
|
||||||
Content: data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTaxAmount(t *testing.T) {
|
|
||||||
taxAmount := GetTaxAmount(12500, 2500)
|
|
||||||
if taxAmount != 2500 {
|
|
||||||
t.Errorf("Expected 2500, got %d\n", taxAmount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddToCartShortCut(t *testing.T) {
|
|
||||||
grain, err := spawn(ToCartId("kalle"))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error spawning: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(grain.Items) != 0 {
|
|
||||||
t.Errorf("Expected 0 items, got %d\n", len(grain.Items))
|
|
||||||
}
|
|
||||||
msg := GetMessage(AddItemType, &messages.AddItem{
|
|
||||||
Quantity: 2,
|
|
||||||
Price: 100,
|
|
||||||
Sku: "123",
|
|
||||||
Name: "Test item",
|
|
||||||
Image: "test.jpg",
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err = grain.HandleMessage(msg, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(grain.Items) != 1 {
|
|
||||||
t.Errorf("Expected 1 item, got %d\n", len(grain.Items))
|
|
||||||
}
|
|
||||||
if grain.Items[0].Quantity != 2 {
|
|
||||||
t.Errorf("Expected quantity 2, got %d\n", grain.Items[0].Quantity)
|
|
||||||
}
|
|
||||||
if len(grain.storageMessages) != 1 {
|
|
||||||
t.Errorf("Expected 1 storage message, got %d\n", len(grain.storageMessages))
|
|
||||||
}
|
|
||||||
shortCutMessage := GetMessage(AddRequestType, &messages.AddRequest{
|
|
||||||
Quantity: 2,
|
|
||||||
Sku: "123",
|
|
||||||
})
|
|
||||||
_, err = grain.HandleMessage(shortCutMessage, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(grain.Items) != 1 {
|
|
||||||
t.Errorf("Expected 1 item, got %d\n", len(grain.Items))
|
|
||||||
}
|
|
||||||
if len(grain.storageMessages) != 2 {
|
|
||||||
t.Errorf("Expected 2 storage message, got %d\n", len(grain.storageMessages))
|
|
||||||
}
|
|
||||||
if grain.storageMessages[0].Type != AddItemType {
|
|
||||||
t.Errorf("Expected AddItemType, got %d\n", grain.storageMessages[0].Type)
|
|
||||||
}
|
|
||||||
if grain.storageMessages[1].Type != AddRequestType {
|
|
||||||
t.Errorf("Expected AddRequestType, got %d\n", grain.storageMessages[1].Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddRequestToGrain(t *testing.T) {
|
|
||||||
grain, err := spawn(ToCartId("kalle"))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error spawning: %v\n", err)
|
|
||||||
}
|
|
||||||
msg := GetMessage(AddRequestType, &messages.AddRequest{
|
|
||||||
Quantity: 2,
|
|
||||||
Sku: "763281",
|
|
||||||
})
|
|
||||||
result, err := grain.HandleMessage(msg, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if result.StatusCode != 200 {
|
|
||||||
t.Errorf("Call failed\n")
|
|
||||||
}
|
|
||||||
t.Log(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddToCart(t *testing.T) {
|
|
||||||
grain, err := spawn(ToCartId("kalle"))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error spawning: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(grain.Items) != 0 {
|
|
||||||
t.Errorf("Expected 0 items, got %d\n", len(grain.Items))
|
|
||||||
}
|
|
||||||
msg := GetMessage(AddItemType, &messages.AddItem{
|
|
||||||
Quantity: 2,
|
|
||||||
Price: 100,
|
|
||||||
Sku: "123",
|
|
||||||
Name: "Test item",
|
|
||||||
Image: "test.jpg",
|
|
||||||
})
|
|
||||||
|
|
||||||
result, err := grain.HandleMessage(msg, false)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if result.StatusCode != 200 {
|
|
||||||
t.Errorf("Call failed\n")
|
|
||||||
}
|
|
||||||
if grain.TotalPrice != 200 {
|
|
||||||
t.Errorf("Expected total price 200, got %d\n", grain.TotalPrice)
|
|
||||||
}
|
|
||||||
if len(grain.Items) != 1 {
|
|
||||||
t.Errorf("Expected 1 item, got %d\n", len(grain.Items))
|
|
||||||
}
|
|
||||||
if grain.Items[0].Quantity != 2 {
|
|
||||||
t.Errorf("Expected quantity 2, got %d\n", grain.Items[0].Quantity)
|
|
||||||
}
|
|
||||||
result, err = grain.HandleMessage(msg, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if result.StatusCode != 200 {
|
|
||||||
t.Errorf("Call failed\n")
|
|
||||||
}
|
|
||||||
if grain.Items[0].Quantity != 4 {
|
|
||||||
t.Errorf("Expected quantity 4, got %d\n", grain.Items[0].Quantity)
|
|
||||||
}
|
|
||||||
if grain.TotalPrice != 400 {
|
|
||||||
t.Errorf("Expected total price 400, got %d\n", grain.TotalPrice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetDelivery(t *testing.T) {
|
|
||||||
grain, err := spawn(ToCartId("kalle"))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error spawning: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(grain.Items) != 0 {
|
|
||||||
t.Errorf("Expected 0 items, got %d\n", len(grain.Items))
|
|
||||||
}
|
|
||||||
msg := GetMessage(AddItemType, &messages.AddItem{
|
|
||||||
Quantity: 2,
|
|
||||||
Price: 100,
|
|
||||||
Sku: "123",
|
|
||||||
Name: "Test item",
|
|
||||||
Image: "test.jpg",
|
|
||||||
})
|
|
||||||
|
|
||||||
grain.HandleMessage(msg, false)
|
|
||||||
|
|
||||||
msg = GetMessage(AddItemType, &messages.AddItem{
|
|
||||||
Quantity: 2,
|
|
||||||
Price: 100,
|
|
||||||
Sku: "123",
|
|
||||||
Name: "Test item",
|
|
||||||
Image: "test.jpg",
|
|
||||||
})
|
|
||||||
|
|
||||||
result, err := grain.HandleMessage(msg, false)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if result.StatusCode != 200 {
|
|
||||||
t.Errorf("Call failed\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
setDelivery := GetMessage(SetDeliveryType, &messages.SetDelivery{
|
|
||||||
Provider: "test",
|
|
||||||
Items: []int64{1},
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err = grain.HandleMessage(setDelivery, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(grain.Deliveries) != 1 {
|
|
||||||
t.Errorf("Expected 1 delivery, got %d\n", len(grain.Deliveries))
|
|
||||||
}
|
|
||||||
if len(grain.Deliveries[0].Items) != 1 {
|
|
||||||
t.Errorf("Expected 1 items in delivery, got %d\n", len(grain.Deliveries[0].Items))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetDeliveryOnAll(t *testing.T) {
|
|
||||||
grain, err := spawn(ToCartId("kalle"))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error spawning: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(grain.Items) != 0 {
|
|
||||||
t.Errorf("Expected 0 items, got %d\n", len(grain.Items))
|
|
||||||
}
|
|
||||||
msg := GetMessage(AddItemType, &messages.AddItem{
|
|
||||||
Quantity: 2,
|
|
||||||
Price: 100,
|
|
||||||
Sku: "123",
|
|
||||||
Name: "Test item",
|
|
||||||
Image: "test.jpg",
|
|
||||||
})
|
|
||||||
|
|
||||||
grain.HandleMessage(msg, false)
|
|
||||||
|
|
||||||
msg = GetMessage(AddItemType, &messages.AddItem{
|
|
||||||
Quantity: 2,
|
|
||||||
Price: 100,
|
|
||||||
Sku: "1233",
|
|
||||||
Name: "Test item2",
|
|
||||||
Image: "test.jpg",
|
|
||||||
})
|
|
||||||
|
|
||||||
result, err := grain.HandleMessage(msg, false)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if result.StatusCode != 200 {
|
|
||||||
t.Errorf("Call failed\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
setDelivery := GetMessage(SetDeliveryType, &messages.SetDelivery{
|
|
||||||
Provider: "test",
|
|
||||||
Items: []int64{},
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err = grain.HandleMessage(setDelivery, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error handling message: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(grain.Deliveries) != 1 {
|
|
||||||
t.Errorf("Expected 1 delivery, got %d\n", len(grain.Deliveries))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(grain.Deliveries[0].Items) != 2 {
|
|
||||||
t.Errorf("Expected 2 items in delivery, got %d\n", len(grain.Deliveries[0].Items))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
276
deployment/deployment-no.yaml
Normal file
276
deployment/deployment-no.yaml
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: klarna-api-credentials
|
||||||
|
data:
|
||||||
|
username: ZjQzZDY3YjEtNzA2Yy00NTk2LTliNTgtYjg1YjU2NDEwZTUw
|
||||||
|
password: a2xhcm5hX3Rlc3RfYXBpX0trUWhWVE5yYVZsV2FsTnhTRVp3Y1ZSSFF5UkVNRmxyY25Kd1AxSndQMGdzWmpRelpEWTNZakV0TnpBMll5MDBOVGsyTFRsaU5UZ3RZamcxWWpVMk5ERXdaVFV3TERFc2JUUkNjRFpWU1RsTllsSk1aMlEyVEc4MmRVODNZMkozUlRaaFdEZDViV3AwYkhGV1JqTjVNQzlaYXow
|
||||||
|
type: Opaque
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: cart-actor
|
||||||
|
arch: amd64
|
||||||
|
name: cart-actor-x86
|
||||||
|
spec:
|
||||||
|
replicas: 0
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: cart-actor
|
||||||
|
arch: amd64
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: cart-actor
|
||||||
|
actor-pool: cart
|
||||||
|
arch: amd64
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/arch
|
||||||
|
operator: NotIn
|
||||||
|
values:
|
||||||
|
- arm64
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
nfs:
|
||||||
|
path: /i-data/7a8af061/nfs/cart-actor-no
|
||||||
|
server: 10.10.1.10
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
serviceAccountName: default
|
||||||
|
containers:
|
||||||
|
- image: registry.knatofs.se/go-cart-actor-amd64:latest
|
||||||
|
name: cart-actor-amd64
|
||||||
|
imagePullPolicy: Always
|
||||||
|
lifecycle:
|
||||||
|
preStop:
|
||||||
|
exec:
|
||||||
|
command: ["sleep", "15"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: web
|
||||||
|
- containerPort: 1234
|
||||||
|
name: echo
|
||||||
|
- containerPort: 1337
|
||||||
|
name: rpc
|
||||||
|
- containerPort: 1338
|
||||||
|
name: quorum
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /livez
|
||||||
|
port: web
|
||||||
|
failureThreshold: 1
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /readyz
|
||||||
|
port: web
|
||||||
|
failureThreshold: 2
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: "/data"
|
||||||
|
name: data
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "768Mi"
|
||||||
|
requests:
|
||||||
|
memory: "70Mi"
|
||||||
|
cpu: "1200m"
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: "Europe/Stockholm"
|
||||||
|
- name: KLARNA_API_USERNAME
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: klarna-api-credentials
|
||||||
|
key: username
|
||||||
|
- name: KLARNA_API_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: klarna-api-credentials
|
||||||
|
key: password
|
||||||
|
- name: POD_IP
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: status.podIP
|
||||||
|
- name: AMQP_URL
|
||||||
|
value: "amqp://admin:12bananer@rabbitmq.dev:5672/"
|
||||||
|
- name: BASE_URL
|
||||||
|
value: "https://s10n-no.tornberg.me"
|
||||||
|
- name: CART_BASE_URL
|
||||||
|
value: "https://cart-no.tornberg.me"
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: cart-actor
|
||||||
|
arch: arm64
|
||||||
|
name: cart-actor-arm64
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: cart-actor
|
||||||
|
arch: arm64
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: cart-actor
|
||||||
|
actor-pool: cart
|
||||||
|
arch: arm64
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: NotIn
|
||||||
|
values:
|
||||||
|
- masterpi
|
||||||
|
- key: kubernetes.io/arch
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm64
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
nfs:
|
||||||
|
path: /i-data/7a8af061/nfs/cart-actor-no
|
||||||
|
server: 10.10.1.10
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
serviceAccountName: default
|
||||||
|
containers:
|
||||||
|
- image: registry.knatofs.se/go-cart-actor:latest
|
||||||
|
name: cart-actor-arm64
|
||||||
|
imagePullPolicy: Always
|
||||||
|
lifecycle:
|
||||||
|
preStop:
|
||||||
|
exec:
|
||||||
|
command: ["sleep", "15"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: web
|
||||||
|
- containerPort: 1234
|
||||||
|
name: echo
|
||||||
|
- containerPort: 1337
|
||||||
|
name: rpc
|
||||||
|
- containerPort: 1338
|
||||||
|
name: quorum
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /livez
|
||||||
|
port: web
|
||||||
|
failureThreshold: 1
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /readyz
|
||||||
|
port: web
|
||||||
|
failureThreshold: 2
|
||||||
|
initialDelaySeconds: 2
|
||||||
|
periodSeconds: 10
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: "/data"
|
||||||
|
name: data
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "768Mi"
|
||||||
|
requests:
|
||||||
|
memory: "70Mi"
|
||||||
|
cpu: "1200m"
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: "Europe/Stockholm"
|
||||||
|
- name: KLARNA_API_USERNAME
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: klarna-api-credentials
|
||||||
|
key: username
|
||||||
|
- name: KLARNA_API_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: klarna-api-credentials
|
||||||
|
key: password
|
||||||
|
- name: POD_IP
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: status.podIP
|
||||||
|
- name: AMQP_URL
|
||||||
|
value: "amqp://admin:12bananer@rabbitmq.dev:5672/"
|
||||||
|
- name: BASE_URL
|
||||||
|
value: "https://s10n-no.tornberg.me"
|
||||||
|
- name: CART_BASE_URL
|
||||||
|
value: "https://cart-no.tornberg.me"
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: cart-echo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: cart-actor
|
||||||
|
type: LoadBalancer
|
||||||
|
ports:
|
||||||
|
- name: echo
|
||||||
|
port: 1234
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: cart-actor
|
||||||
|
annotations:
|
||||||
|
prometheus.io/port: "8080"
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: cart-actor
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
port: 8080
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: cart-ingress
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
# nginx.ingress.kubernetes.io/affinity: "cookie"
|
||||||
|
# nginx.ingress.kubernetes.io/session-cookie-name: "cart-affinity"
|
||||||
|
# nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
|
||||||
|
# nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: 4m
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- cart-no.tornberg.me
|
||||||
|
secretName: cart-actor-no-tls-secret
|
||||||
|
rules:
|
||||||
|
- host: cart-no.tornberg.me
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: cart-actor
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
@@ -15,7 +15,7 @@ metadata:
|
|||||||
arch: amd64
|
arch: amd64
|
||||||
name: cart-actor-x86
|
name: cart-actor-x86
|
||||||
spec:
|
spec:
|
||||||
replicas: 3
|
replicas: 0
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: cart-actor
|
app: cart-actor
|
||||||
@@ -55,6 +55,8 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
name: web
|
name: web
|
||||||
|
- containerPort: 1234
|
||||||
|
name: echo
|
||||||
- containerPort: 1337
|
- containerPort: 1337
|
||||||
name: rpc
|
name: rpc
|
||||||
- containerPort: 1338
|
- containerPort: 1338
|
||||||
@@ -98,6 +100,10 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: status.podIP
|
fieldPath: status.podIP
|
||||||
|
- name: AMQP_URL
|
||||||
|
value: "amqp://admin:12bananer@rabbitmq.dev:5672/"
|
||||||
|
# - name: BASE_URL
|
||||||
|
# value: "https://s10n-no.tornberg.me"
|
||||||
- name: POD_NAME
|
- name: POD_NAME
|
||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
@@ -155,6 +161,8 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
name: web
|
name: web
|
||||||
|
- containerPort: 1234
|
||||||
|
name: echo
|
||||||
- containerPort: 1337
|
- containerPort: 1337
|
||||||
name: rpc
|
name: rpc
|
||||||
- containerPort: 1338
|
- containerPort: 1338
|
||||||
@@ -198,6 +206,10 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: status.podIP
|
fieldPath: status.podIP
|
||||||
|
- name: AMQP_URL
|
||||||
|
value: "amqp://admin:12bananer@rabbitmq.dev:5672/"
|
||||||
|
# - name: BASE_URL
|
||||||
|
# value: "https://s10n-no.tornberg.me"
|
||||||
- name: POD_NAME
|
- name: POD_NAME
|
||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
@@ -205,6 +217,18 @@ spec:
|
|||||||
---
|
---
|
||||||
kind: Service
|
kind: Service
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: cart-echo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: cart-actor
|
||||||
|
type: LoadBalancer
|
||||||
|
ports:
|
||||||
|
- name: echo
|
||||||
|
port: 1234
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
metadata:
|
metadata:
|
||||||
name: cart-actor
|
name: cart-actor
|
||||||
annotations:
|
annotations:
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DiscardedHost struct {
|
type DiscardedHost struct {
|
||||||
*Connection
|
|
||||||
Host string
|
Host string
|
||||||
Tries int
|
Tries int
|
||||||
}
|
}
|
||||||
@@ -26,7 +25,7 @@ func (d *DiscardedHostHandler) run() {
|
|||||||
d.mu.RLock()
|
d.mu.RLock()
|
||||||
lst := make([]*DiscardedHost, 0, len(d.hosts))
|
lst := make([]*DiscardedHost, 0, len(d.hosts))
|
||||||
for _, host := range d.hosts {
|
for _, host := range d.hosts {
|
||||||
if host.Tries >= 0 || host.Tries < 5 {
|
if host.Tries >= 0 && host.Tries < 5 {
|
||||||
go d.testConnection(host)
|
go d.testConnection(host)
|
||||||
lst = append(lst, host)
|
lst = append(lst, host)
|
||||||
} else {
|
} else {
|
||||||
@@ -49,7 +48,9 @@ func (d *DiscardedHostHandler) testConnection(host *DiscardedHost) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
host.Tries++
|
host.Tries++
|
||||||
host.Tries = -1
|
if host.Tries >= 5 {
|
||||||
|
// Exceeded retry threshold; will be dropped by run loop.
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
if d.onConnection != nil {
|
if d.onConnection != nil {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDiscardedHost(t *testing.T) {
|
|
||||||
dh := NewDiscardedHostHandler(8080)
|
|
||||||
dh.SetReconnectHandler(func(host string) {
|
|
||||||
t.Log(host)
|
|
||||||
})
|
|
||||||
dh.AppendHost("localhost")
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
if dh.hosts[0].Tries == 0 {
|
|
||||||
t.Error("Host not tested")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDiscovery(t *testing.T) {
|
func TestDiscovery(t *testing.T) {
|
||||||
config, err := clientcmd.BuildConfigFromFlags("", "/Users/mats/.kube/config")
|
config, err := clientcmd.BuildConfigFromFlags("", "/home/mats/.kube/config")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error building config: %v", err)
|
t.Errorf("Error building config: %v", err)
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ func TestDiscovery(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
config, err := clientcmd.BuildConfigFromFlags("", "/Users/mats/.kube/config")
|
config, err := clientcmd.BuildConfigFromFlags("", "/home/mats/.kube/config")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error building config: %v", err)
|
t.Errorf("Error building config: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
102
frames.go
Normal file
102
frames.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// Minimal frame abstractions retained after removal of the legacy TCP/frame
|
||||||
|
// networking layer. These types remain only to avoid a wide cascading refactor
|
||||||
|
// across existing grain / pool logic that still constructs and passes
|
||||||
|
// FrameWithPayload objects internally.
|
||||||
|
//
|
||||||
|
// The original responsibilities this replaces:
|
||||||
|
// - Binary framing, checksums, network IO
|
||||||
|
// - Distinction between request / reply frame types
|
||||||
|
//
|
||||||
|
// What remains:
|
||||||
|
// - A light weight container (FrameWithPayload) used as an in‑process
|
||||||
|
// envelope for status code + typed marker + payload bytes (JSON or proto).
|
||||||
|
// - Message / status constants referenced in existing code paths.
|
||||||
|
//
|
||||||
|
// Recommended future cleanup (post‑migration):
|
||||||
|
// - Remove FrameType entirely and replace with enumerated semantic results
|
||||||
|
// or error values.
|
||||||
|
// - Replace FrameWithPayload with a struct { Status int; Data []byte }.
|
||||||
|
// - Remove remote_* reply type branching once all callers rely on gRPC
|
||||||
|
// status + strongly typed responses.
|
||||||
|
//
|
||||||
|
// For now we keep this minimal surface to keep the gRPC migration focused.
|
||||||
|
|
||||||
|
type (
|
||||||
|
// FrameType is a symbolic identifier carried through existing code paths.
|
||||||
|
// No ordering or bit semantics are required anymore.
|
||||||
|
FrameType uint32
|
||||||
|
StatusCode uint32
|
||||||
|
)
|
||||||
|
|
||||||
|
type Frame struct {
|
||||||
|
Type FrameType
|
||||||
|
StatusCode StatusCode
|
||||||
|
Length uint32
|
||||||
|
// Checksum retained for compatibility; no longer validated.
|
||||||
|
Checksum uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// FrameWithPayload wraps a Frame with an opaque payload.
|
||||||
|
// Payload usually contains JSON encoded cart state or an error message.
|
||||||
|
type FrameWithPayload struct {
|
||||||
|
Frame
|
||||||
|
Payload []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Legacy Frame Type Constants (minimal subset still referenced)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
const (
|
||||||
|
RemoteGetState = FrameType(0x01)
|
||||||
|
RemoteHandleMutation = FrameType(0x02)
|
||||||
|
ResponseBody = FrameType(0x03) // (rarely used; kept for completeness)
|
||||||
|
RemoteGetStateReply = FrameType(0x04)
|
||||||
|
RemoteHandleMutationReply = FrameType(0x05)
|
||||||
|
RemoteCreateOrderReply = FrameType(0x06)
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeFrameWithPayload constructs an in‑process frame wrapper.
|
||||||
|
// Length & Checksum are filled for backward compatibility (no validation logic
|
||||||
|
// depends on the checksum anymore).
|
||||||
|
func MakeFrameWithPayload(msg FrameType, statusCode StatusCode, payload []byte) FrameWithPayload {
|
||||||
|
length := uint32(len(payload))
|
||||||
|
return FrameWithPayload{
|
||||||
|
Frame: Frame{
|
||||||
|
Type: msg,
|
||||||
|
StatusCode: statusCode,
|
||||||
|
Length: length,
|
||||||
|
Checksum: (uint32(msg) + uint32(statusCode) + length) / 8, // simple legacy formula
|
||||||
|
},
|
||||||
|
Payload: payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a shallow copy of the frame, duplicating the payload slice.
|
||||||
|
func (f *FrameWithPayload) Clone() *FrameWithPayload {
|
||||||
|
if f == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cp := make([]byte, len(f.Payload))
|
||||||
|
copy(cp, f.Payload)
|
||||||
|
return &FrameWithPayload{
|
||||||
|
Frame: f.Frame,
|
||||||
|
Payload: cp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorFrame helper for creating an error frame with a textual payload.
|
||||||
|
func NewErrorFrame(msg FrameType, code StatusCode, err error) FrameWithPayload {
|
||||||
|
var b []byte
|
||||||
|
if err != nil {
|
||||||
|
b = []byte(err.Error())
|
||||||
|
}
|
||||||
|
return MakeFrameWithPayload(msg, code, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess returns true if the status code indicates success in the
|
||||||
|
// conventional HTTP style range (200–299). This mirrors previous usage patterns.
|
||||||
|
func (f *FrameWithPayload) IsSuccess() bool {
|
||||||
|
return f != nil && f.StatusCode >= 200 && f.StatusCode < 300
|
||||||
|
}
|
||||||
95
go.mod
95
go.mod
@@ -1,58 +1,73 @@
|
|||||||
module git.tornberg.me/go-cart-actor
|
module git.tornberg.me/go-cart-actor
|
||||||
|
|
||||||
go 1.23.3
|
go 1.25.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/prometheus/client_golang v1.20.5
|
github.com/google/uuid v1.6.0
|
||||||
google.golang.org/protobuf v1.34.2
|
github.com/matst80/slask-finder v0.0.0-20251009175145-ce05aff5a548
|
||||||
k8s.io/api v0.31.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
k8s.io/apimachinery v0.31.2
|
github.com/rabbitmq/amqp091-go v1.10.0
|
||||||
k8s.io/client-go v0.31.2
|
google.golang.org/grpc v1.76.0
|
||||||
|
google.golang.org/protobuf v1.36.10
|
||||||
|
k8s.io/api v0.34.1
|
||||||
|
k8s.io/apimachinery v0.34.1
|
||||||
|
k8s.io/client-go v0.34.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Flaconi/go-klarna v0.0.0-20230216165926-e2f708c721d9 // indirect
|
github.com/RoaringBitmap/roaring/v2 v2.10.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bits-and-blooms/bitset v1.24.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||||
github.com/go-openapi/swag v0.22.4 // indirect
|
github.com/go-openapi/swag v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/cmdutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/conv v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/fileutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/mangling v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/netutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/google/gnostic-models v0.7.0 // indirect
|
||||||
github.com/google/gnostic-models v0.6.8 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/gorilla/schema v1.4.1 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/imdario/mergo v0.3.6 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/common v0.55.0 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/common v0.67.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/prometheus/procfs v0.17.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
golang.org/x/net v0.26.0 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/oauth2 v0.21.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
golang.org/x/term v0.21.0 // indirect
|
golang.org/x/oauth2 v0.32.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/term v0.36.0 // indirect
|
||||||
|
golang.org/x/text v0.30.0 // indirect
|
||||||
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect
|
||||||
|
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
|
||||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
245
go.sum
245
go.sum
@@ -1,107 +1,144 @@
|
|||||||
github.com/Flaconi/go-klarna v0.0.0-20230216165926-e2f708c721d9 h1:U5gu3M9/khqtvgg6iRKo0+nxGEfPHWFHRlKrbZvFxIY=
|
github.com/RoaringBitmap/roaring/v2 v2.10.0 h1:HbJ8Cs71lfCJyvmSptxeMX2PtvOC8yonlU0GQcy2Ak0=
|
||||||
github.com/Flaconi/go-klarna v0.0.0-20230216165926-e2f708c721d9/go.mod h1:+LVFV9FXH5cwN1VcU30WcNYRs5FhkEtL7/IqqTD42cU=
|
github.com/RoaringBitmap/roaring/v2 v2.10.0/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
|
github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM=
|
||||||
|
github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
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/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
|
||||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
|
||||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
|
||||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
|
||||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8=
|
||||||
|
github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo=
|
||||||
|
github.com/go-openapi/swag/cmdutils v0.25.1 h1:nDke3nAFDArAa631aitksFGj2omusks88GF1VwdYqPY=
|
||||||
|
github.com/go-openapi/swag/cmdutils v0.25.1/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
|
||||||
|
github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
|
||||||
|
github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
|
||||||
|
github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU=
|
||||||
|
github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M=
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
|
||||||
|
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY=
|
||||||
|
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg=
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
|
||||||
|
github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync=
|
||||||
|
github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ=
|
||||||
|
github.com/go-openapi/swag/netutils v0.25.1 h1:2wFLYahe40tDUHfKT1GRC4rfa5T1B4GWZ+msEFA4Fl4=
|
||||||
|
github.com/go-openapi/swag/netutils v0.25.1/go.mod h1:CAkkvqnUJX8NV96tNhEQvKz8SQo2KF0f7LleiJwIeRE=
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
|
|
||||||
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/matst80/slask-finder v0.0.0-20251009175145-ce05aff5a548 h1:lkxVz5lNPlU78El49cx1c3Rxo/bp7Gbzp2BjSRFgg1U=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/matst80/slask-finder v0.0.0-20251009175145-ce05aff5a548/go.mod h1:k4lo5gFYb3AqrgftlraCnv95xvjB/w8udRqJQJ12mAE=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
|
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
|
||||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||||
|
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
|
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=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
@@ -111,66 +148,72 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff h1:A90eA31Wq6HOMIQlLfzFwzqGKBTuaVztYu/g8sn+8Zc=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
|
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||||
|
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
|
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
|
||||||
k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
|
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
|
||||||
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
|
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
|
||||||
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||||
k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc=
|
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
|
||||||
k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs=
|
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
|
||||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
|
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
|
||||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
||||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
|
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
||||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||||
|
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||||
|
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||||
|
|||||||
@@ -129,8 +129,9 @@ func (p *GrainLocalPool) GetGrain(id CartId) (*CartGrain, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
grain, err = p.spawn(id)
|
grain, err = p.spawn(id)
|
||||||
|
p.mu.Lock()
|
||||||
p.grains[id] = grain
|
p.grains[id] = grain
|
||||||
|
p.mu.Unlock()
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
l := float64(len(p.grains))
|
l := float64(len(p.grains))
|
||||||
|
|||||||
135
grpc_integration_test.go
Normal file
135
grpc_integration_test.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
messages "git.tornberg.me/go-cart-actor/proto"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestCartActorMutationAndState validates end-to-end gRPC mutation + state retrieval
|
||||||
|
// against a locally started gRPC server (single-node scenario).
|
||||||
|
// This test uses AddItemType directly to avoid hitting external product
|
||||||
|
// fetching logic (FetchItem) which would require network I/O.
|
||||||
|
func TestCartActorMutationAndState(t *testing.T) {
|
||||||
|
// Setup local grain pool + synced pool (no discovery, single host)
|
||||||
|
pool := NewGrainLocalPool(1024, time.Minute, spawn)
|
||||||
|
synced, err := NewSyncedPool(pool, "127.0.0.1", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewSyncedPool error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start gRPC server (CartActor + ControlPlane) on :1337
|
||||||
|
grpcSrv, err := StartGRPCServer(":1337", pool, synced)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("StartGRPCServer error: %v", err)
|
||||||
|
}
|
||||||
|
defer grpcSrv.GracefulStop()
|
||||||
|
|
||||||
|
// Dial the local server
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
conn, err := grpc.DialContext(ctx, "127.0.0.1:1337",
|
||||||
|
grpc.WithInsecure(),
|
||||||
|
grpc.WithBlock(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("grpc.Dial error: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
cartClient := messages.NewCartActorClient(conn)
|
||||||
|
|
||||||
|
// Create a short cart id (<=16 chars so it fits into the fixed CartId 16-byte array cleanly)
|
||||||
|
cartID := fmt.Sprintf("cart-%d", time.Now().UnixNano())
|
||||||
|
|
||||||
|
// Build an AddItem payload (bypasses FetchItem to keep test deterministic)
|
||||||
|
addItem := &messages.AddItem{
|
||||||
|
ItemId: 1,
|
||||||
|
Quantity: 1,
|
||||||
|
Price: 1000,
|
||||||
|
OrgPrice: 1000,
|
||||||
|
Sku: "test-sku",
|
||||||
|
Name: "Test SKU",
|
||||||
|
Image: "/img.png",
|
||||||
|
Stock: 2, // InStock
|
||||||
|
Tax: 2500,
|
||||||
|
Country: "se",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal underlying mutation payload using the existing handler code path
|
||||||
|
handler, ok := Handlers[AddItemType]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Handler for AddItemType missing")
|
||||||
|
}
|
||||||
|
payloadData, err := getSerializedPayload(handler, AddItemType, addItem)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("serialize add item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue Mutate RPC
|
||||||
|
mutResp, err := cartClient.Mutate(context.Background(), &messages.MutationRequest{
|
||||||
|
CartId: cartID,
|
||||||
|
Type: messages.MutationType(AddItemType),
|
||||||
|
Payload: payloadData,
|
||||||
|
ClientTimestamp: time.Now().Unix(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Mutate RPC error: %v", err)
|
||||||
|
}
|
||||||
|
if mutResp.StatusCode != 200 {
|
||||||
|
t.Fatalf("Mutate returned non-200 status: %d payload=%s", mutResp.StatusCode, string(mutResp.Payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode cart state JSON and validate
|
||||||
|
state := &CartGrain{}
|
||||||
|
if err := json.Unmarshal(mutResp.Payload, state); err != nil {
|
||||||
|
t.Fatalf("Unmarshal mutate cart state: %v\nPayload: %s", err, string(mutResp.Payload))
|
||||||
|
}
|
||||||
|
if len(state.Items) != 1 {
|
||||||
|
t.Fatalf("Expected 1 item after mutation, got %d", len(state.Items))
|
||||||
|
}
|
||||||
|
if state.Items[0].Sku != "test-sku" {
|
||||||
|
t.Fatalf("Unexpected item SKU: %s", state.Items[0].Sku)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue GetState RPC
|
||||||
|
getResp, err := cartClient.GetState(context.Background(), &messages.StateRequest{
|
||||||
|
CartId: cartID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetState RPC error: %v", err)
|
||||||
|
}
|
||||||
|
if getResp.StatusCode != 200 {
|
||||||
|
t.Fatalf("GetState returned non-200 status: %d payload=%s", getResp.StatusCode, string(getResp.Payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
state2 := &CartGrain{}
|
||||||
|
if err := json.Unmarshal(getResp.Payload, state2); err != nil {
|
||||||
|
t.Fatalf("Unmarshal get state: %v", err)
|
||||||
|
}
|
||||||
|
if len(state2.Items) != 1 {
|
||||||
|
t.Fatalf("Expected 1 item in GetState, got %d", len(state2.Items))
|
||||||
|
}
|
||||||
|
if state2.Items[0].Sku != "test-sku" {
|
||||||
|
t.Fatalf("Unexpected SKU in GetState: %s", state2.Items[0].Sku)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSerializedPayload serializes a mutation proto using the registered handler.
|
||||||
|
func getSerializedPayload(handler MessageHandler, msgType uint16, content interface{}) ([]byte, error) {
|
||||||
|
msg := &Message{
|
||||||
|
Type: msgType,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := handler.Write(msg, &buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
379
grpc_server.go
Normal file
379
grpc_server.go
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
proto "git.tornberg.me/go-cart-actor/proto" // underlying generated package name is 'messages'
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Metrics
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var (
|
||||||
|
grpcMutateDuration = promauto.NewHistogram(prometheus.HistogramOpts{
|
||||||
|
Name: "cart_grpc_mutate_duration_seconds",
|
||||||
|
Help: "Duration of CartActor.Mutate RPCs",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
})
|
||||||
|
grpcMutateErrors = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "cart_grpc_mutate_errors_total",
|
||||||
|
Help: "Total number of failed CartActor.Mutate RPCs",
|
||||||
|
})
|
||||||
|
grpcStateDuration = promauto.NewHistogram(prometheus.HistogramOpts{
|
||||||
|
Name: "cart_grpc_get_state_duration_seconds",
|
||||||
|
Help: "Duration of CartActor.GetState RPCs",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
})
|
||||||
|
grpcControlDuration = promauto.NewHistogram(prometheus.HistogramOpts{
|
||||||
|
Name: "cart_grpc_control_duration_seconds",
|
||||||
|
Help: "Duration of ControlPlane RPCs",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
})
|
||||||
|
grpcControlErrors = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
|
Name: "cart_grpc_control_errors_total",
|
||||||
|
Help: "Total number of failed ControlPlane RPCs",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// timeTrack wraps a closure and records duration into the supplied histogram.
|
||||||
|
func timeTrack(hist prometheus.Observer, fn func() error) (err error) {
|
||||||
|
start := time.Now()
|
||||||
|
defer func() {
|
||||||
|
hist.Observe(time.Since(start).Seconds())
|
||||||
|
}()
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// CartActor Service Implementation
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type cartActorService struct {
|
||||||
|
proto.UnimplementedCartActorServer
|
||||||
|
pool GrainPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCartActorService(pool GrainPool) *cartActorService {
|
||||||
|
return &cartActorService{pool: pool}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *cartActorService) Mutate(ctx context.Context, req *proto.MutationRequest) (*proto.MutationReply, error) {
|
||||||
|
var reply *proto.MutationReply
|
||||||
|
err := timeTrack(grpcMutateDuration, func() error {
|
||||||
|
if req == nil {
|
||||||
|
return status.Error(codes.InvalidArgument, "request is nil")
|
||||||
|
}
|
||||||
|
if req.CartId == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "cart_id is empty")
|
||||||
|
}
|
||||||
|
mt := uint16(req.Type.Number())
|
||||||
|
handler, ok := Handlers[mt]
|
||||||
|
if !ok {
|
||||||
|
return status.Errorf(codes.InvalidArgument, "unknown mutation type %d", mt)
|
||||||
|
}
|
||||||
|
content, err := handler.Read(req.Payload)
|
||||||
|
if err != nil {
|
||||||
|
return status.Errorf(codes.InvalidArgument, "decode payload: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := req.ClientTimestamp
|
||||||
|
if ts == 0 {
|
||||||
|
ts = time.Now().Unix()
|
||||||
|
}
|
||||||
|
msg := Message{
|
||||||
|
Type: mt,
|
||||||
|
TimeStamp: &ts,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
|
||||||
|
frame, err := s.pool.Process(ToCartId(req.CartId), msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reply = &proto.MutationReply{
|
||||||
|
StatusCode: int32(frame.StatusCode),
|
||||||
|
Payload: frame.Payload,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
grpcMutateErrors.Inc()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *cartActorService) GetState(ctx context.Context, req *proto.StateRequest) (*proto.StateReply, error) {
|
||||||
|
var reply *proto.StateReply
|
||||||
|
err := timeTrack(grpcStateDuration, func() error {
|
||||||
|
if req == nil || req.CartId == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "cart_id is empty")
|
||||||
|
}
|
||||||
|
frame, err := s.pool.Get(ToCartId(req.CartId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reply = &proto.StateReply{
|
||||||
|
StatusCode: int32(frame.StatusCode),
|
||||||
|
Payload: frame.Payload,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// ControlPlane Service Implementation
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// controlPlaneService directly leverages SyncedPool internals (same package).
|
||||||
|
// NOTE: This is a transitional adapter; once the legacy frame-based code is
|
||||||
|
// removed, related fields/methods in SyncedPool can be slimmed.
|
||||||
|
type controlPlaneService struct {
|
||||||
|
proto.UnimplementedControlPlaneServer
|
||||||
|
pool *SyncedPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newControlPlaneService(pool *SyncedPool) *controlPlaneService {
|
||||||
|
return &controlPlaneService{pool: pool}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *controlPlaneService) Ping(ctx context.Context, _ *proto.Empty) (*proto.PingReply, error) {
|
||||||
|
var reply *proto.PingReply
|
||||||
|
err := timeTrack(grpcControlDuration, func() error {
|
||||||
|
reply = &proto.PingReply{
|
||||||
|
Host: s.pool.Hostname,
|
||||||
|
UnixTime: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
grpcControlErrors.Inc()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *controlPlaneService) Negotiate(ctx context.Context, req *proto.NegotiateRequest) (*proto.NegotiateReply, error) {
|
||||||
|
var reply *proto.NegotiateReply
|
||||||
|
err := timeTrack(grpcControlDuration, func() error {
|
||||||
|
if req == nil {
|
||||||
|
return status.Error(codes.InvalidArgument, "request is nil")
|
||||||
|
}
|
||||||
|
// Add unknown hosts
|
||||||
|
for _, host := range req.KnownHosts {
|
||||||
|
if host == "" || host == s.pool.Hostname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !s.pool.IsKnown(host) {
|
||||||
|
go s.pool.AddRemote(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Build healthy host list
|
||||||
|
hosts := make([]string, 0)
|
||||||
|
for _, r := range s.pool.GetHealthyRemotes() {
|
||||||
|
hosts = append(hosts, r.Host)
|
||||||
|
}
|
||||||
|
hosts = append(hosts, s.pool.Hostname)
|
||||||
|
reply = &proto.NegotiateReply{
|
||||||
|
Hosts: hosts,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
grpcControlErrors.Inc()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *controlPlaneService) GetCartIds(ctx context.Context, _ *proto.Empty) (*proto.CartIdsReply, error) {
|
||||||
|
var reply *proto.CartIdsReply
|
||||||
|
err := timeTrack(grpcControlDuration, func() error {
|
||||||
|
s.pool.mu.RLock()
|
||||||
|
defer s.pool.mu.RUnlock()
|
||||||
|
ids := make([]string, 0, len(s.pool.local.grains))
|
||||||
|
for id, g := range s.pool.local.grains {
|
||||||
|
if g == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if id.String() == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, id.String())
|
||||||
|
}
|
||||||
|
reply = &proto.CartIdsReply{
|
||||||
|
CartIds: ids,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
grpcControlErrors.Inc()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *controlPlaneService) ConfirmOwner(ctx context.Context, req *proto.OwnerChangeRequest) (*proto.OwnerChangeAck, error) {
|
||||||
|
var reply *proto.OwnerChangeAck
|
||||||
|
err := timeTrack(grpcControlDuration, func() error {
|
||||||
|
if req == nil || req.CartId == "" || req.NewHost == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "cart_id or new_host missing")
|
||||||
|
}
|
||||||
|
id := ToCartId(req.CartId)
|
||||||
|
newHost := req.NewHost
|
||||||
|
|
||||||
|
// Mirror GrainOwnerChangeHandler semantics
|
||||||
|
log.Printf("gRPC ConfirmOwner: cart %s newHost=%s", id, newHost)
|
||||||
|
for _, r := range s.pool.remoteHosts {
|
||||||
|
if r.Host == newHost && r.IsHealthy() {
|
||||||
|
go s.pool.SpawnRemoteGrain(id, newHost)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go s.pool.AddRemote(newHost)
|
||||||
|
|
||||||
|
reply = &proto.OwnerChangeAck{
|
||||||
|
Accepted: true,
|
||||||
|
Message: "ok",
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
grpcControlErrors.Inc()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *controlPlaneService) Closing(ctx context.Context, notice *proto.ClosingNotice) (*proto.OwnerChangeAck, error) {
|
||||||
|
var reply *proto.OwnerChangeAck
|
||||||
|
err := timeTrack(grpcControlDuration, func() error {
|
||||||
|
if notice == nil || notice.Host == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "host missing")
|
||||||
|
}
|
||||||
|
host := notice.Host
|
||||||
|
s.pool.mu.RLock()
|
||||||
|
_, exists := s.pool.remoteHosts[host]
|
||||||
|
s.pool.mu.RUnlock()
|
||||||
|
if exists {
|
||||||
|
go s.pool.RemoveHost(host)
|
||||||
|
}
|
||||||
|
reply = &proto.OwnerChangeAck{
|
||||||
|
Accepted: true,
|
||||||
|
Message: "removed",
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
grpcControlErrors.Inc()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Server Bootstrap
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type GRPCServer struct {
|
||||||
|
server *grpc.Server
|
||||||
|
lis net.Listener
|
||||||
|
addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartGRPCServer sets up a gRPC server hosting both CartActor and ControlPlane services.
|
||||||
|
// addr example: ":1337" (for combined) OR run two servers if you want separate ports.
|
||||||
|
// For the migration we can host both on the same listener to reduce open ports.
|
||||||
|
func StartGRPCServer(addr string, pool GrainPool, synced *SyncedPool, opts ...grpc.ServerOption) (*GRPCServer, error) {
|
||||||
|
if pool == nil {
|
||||||
|
return nil, errors.New("nil grain pool")
|
||||||
|
}
|
||||||
|
if synced == nil {
|
||||||
|
return nil, errors.New("nil synced pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
lis, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listen %s: %w", addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcServer := grpc.NewServer(opts...)
|
||||||
|
proto.RegisterCartActorServer(grpcServer, newCartActorService(pool))
|
||||||
|
proto.RegisterControlPlaneServer(grpcServer, newControlPlaneService(synced))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Printf("gRPC server listening on %s", addr)
|
||||||
|
if serveErr := grpcServer.Serve(lis); serveErr != nil {
|
||||||
|
log.Printf("gRPC server stopped: %v", serveErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &GRPCServer{
|
||||||
|
server: grpcServer,
|
||||||
|
lis: lis,
|
||||||
|
addr: addr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GracefulStop stops the server gracefully.
|
||||||
|
func (s *GRPCServer) GracefulStop() {
|
||||||
|
if s == nil || s.server == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.server.GracefulStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the bound address.
|
||||||
|
func (s *GRPCServer) Addr() string {
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Client Dial Helpers (used later by refactored remote grain + control plane)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// DialRemote establishes (or reuses externally) a gRPC client connection.
|
||||||
|
func DialRemote(ctx context.Context, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||||
|
dialOpts := []grpc.DialOption{
|
||||||
|
grpc.WithInsecure(), // NOTE: Intentional for initial migration; replace with TLS / mTLS later.
|
||||||
|
grpc.WithBlock(),
|
||||||
|
}
|
||||||
|
dialOpts = append(dialOpts, opts...)
|
||||||
|
ctxDial, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
conn, err := grpc.DialContext(ctxDial, target, dialOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Utility for converting internal errors to gRPC status (if needed later).
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func grpcError(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Extend mapping if we add richer error types.
|
||||||
|
return status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
128
klarna-client.go
Normal file
128
klarna-client.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KlarnaClient struct {
|
||||||
|
Url string
|
||||||
|
UserName string
|
||||||
|
Password string
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKlarnaClient(url, userName, password string) *KlarnaClient {
|
||||||
|
return &KlarnaClient{
|
||||||
|
Url: url,
|
||||||
|
UserName: userName,
|
||||||
|
Password: password,
|
||||||
|
client: &http.Client{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
KlarnaPlaygroundUrl = "https://api.playground.klarna.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (k *KlarnaClient) GetOrder(orderId string) (*CheckoutOrder, error) {
|
||||||
|
req, err := http.NewRequest("GET", k.Url+"/checkout/v3/orders/"+orderId, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
req.SetBasicAuth(k.UserName, k.Password)
|
||||||
|
|
||||||
|
res, err := k.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
return k.getOrderResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KlarnaClient) getOrderResponse(res *http.Response) (*CheckoutOrder, error) {
|
||||||
|
var err error
|
||||||
|
var klarnaOrderResponse CheckoutOrder
|
||||||
|
if res.StatusCode >= 200 && res.StatusCode <= 299 {
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&klarnaOrderResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &klarnaOrderResponse, nil
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err == nil {
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("%s", res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KlarnaClient) CreateOrder(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.Header.Add("Content-Type", "application/json")
|
||||||
|
req.SetBasicAuth(k.UserName, k.Password)
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
return k.getOrderResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KlarnaClient) UpdateOrder(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.Header.Add("Content-Type", "application/json")
|
||||||
|
req.SetBasicAuth(k.UserName, k.Password)
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if nil != err {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
return k.getOrderResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KlarnaClient) AbortOrder(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.SetBasicAuth(k.UserName, k.Password)
|
||||||
|
|
||||||
|
_, err = http.DefaultClient.Do(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ordermanagement/v1/orders/{order_id}/acknowledge
|
||||||
|
func (k *KlarnaClient) AcknowledgeOrder(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
|
||||||
|
}
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
req.SetBasicAuth(k.UserName, k.Password)
|
||||||
|
req.Header.Add("Klarna-Idempotency-Key", id.String())
|
||||||
|
|
||||||
|
_, err = http.DefaultClient.Do(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
169
klarna-types.go
Normal file
169
klarna-types.go
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type (
|
||||||
|
LineType string
|
||||||
|
|
||||||
|
// CheckoutOrder type is the request structure to create a new order from the Checkout API
|
||||||
|
CheckoutOrder struct {
|
||||||
|
ID string `json:"order_id,omitempty"`
|
||||||
|
PurchaseCountry string `json:"purchase_country"`
|
||||||
|
PurchaseCurrency string `json:"purchase_currency"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
BillingAddress *Address `json:"billing_address,omitempty"`
|
||||||
|
ShippingAddress *Address `json:"shipping_address,omitempty"`
|
||||||
|
OrderAmount int `json:"order_amount"`
|
||||||
|
OrderTaxAmount int `json:"order_tax_amount"`
|
||||||
|
OrderLines []*Line `json:"order_lines"`
|
||||||
|
Customer *CheckoutCustomer `json:"customer,omitempty"`
|
||||||
|
MerchantURLS *CheckoutMerchantURLS `json:"merchant_urls"`
|
||||||
|
HTMLSnippet string `json:"html_snippet,omitempty"`
|
||||||
|
MerchantReference1 string `json:"merchant_reference1,omitempty"`
|
||||||
|
MerchantReference2 string `json:"merchant_reference2,omitempty"`
|
||||||
|
StartedAt string `json:"started_at,omitempty"`
|
||||||
|
CompletedAt string `json:"completed_at,omitempty"`
|
||||||
|
LastModifiedAt string `json:"last_modified_at,omitempty"`
|
||||||
|
Options *CheckoutOptions `json:"options,omitempty"`
|
||||||
|
Attachment *Attachment `json:"attachment,omitempty"`
|
||||||
|
ExternalPaymentMethods []*PaymentProvider `json:"external_payment_methods,omitempty"`
|
||||||
|
ExternalCheckouts []*PaymentProvider `json:"external_checkouts,omitempty"`
|
||||||
|
ShippingCountries []string `json:"shipping_countries,omitempty"`
|
||||||
|
ShippingOptions []*ShippingOption `json:"shipping_options,omitempty"`
|
||||||
|
MerchantData string `json:"merchant_data,omitempty"`
|
||||||
|
GUI *GUI `json:"gui,omitempty"`
|
||||||
|
MerchantRequested *AdditionalCheckBox `json:"merchant_requested,omitempty"`
|
||||||
|
SelectedShippingOption *ShippingOption `json:"selected_shipping_option,omitempty"`
|
||||||
|
ErrorCode string `json:"error_code,omitempty"`
|
||||||
|
ErrorMessages []string `json:"error_messages,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GUI type wraps the GUI options
|
||||||
|
GUI struct {
|
||||||
|
Options []string `json:"options,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShippingOption type is part of the CheckoutOrder structure, represent the shipping options field
|
||||||
|
ShippingOption struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Promo string `json:"promo,omitempty"`
|
||||||
|
Price int `json:"price"`
|
||||||
|
TaxAmount int `json:"tax_amount"`
|
||||||
|
TaxRate int `json:"tax_rate"`
|
||||||
|
Preselected bool `json:"preselected,omitempty"`
|
||||||
|
ShippingMethod string `json:"shipping_method,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaymentProvider type is part of the CheckoutOrder structure, represent the ExternalPaymentMethods and
|
||||||
|
// ExternalCheckouts field
|
||||||
|
PaymentProvider struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
RedirectURL string `json:"redirect_url"`
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
Fee int `json:"fee,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Countries []string `json:"countries,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Attachment struct {
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckoutOptions struct {
|
||||||
|
AcquiringChannel string `json:"acquiring_channel,omitempty"`
|
||||||
|
AllowSeparateShippingAddress bool `json:"allow_separate_shipping_address,omitempty"`
|
||||||
|
ColorButton string `json:"color_button,omitempty"`
|
||||||
|
ColorButtonText string `json:"color_button_text,omitempty"`
|
||||||
|
ColorCheckbox string `json:"color_checkbox,omitempty"`
|
||||||
|
ColorCheckboxCheckmark string `json:"color_checkbox_checkmark,omitempty"`
|
||||||
|
ColorHeader string `json:"color_header,omitempty"`
|
||||||
|
ColorLink string `json:"color_link,omitempty"`
|
||||||
|
DateOfBirthMandatory bool `json:"date_of_birth_mandatory,omitempty"`
|
||||||
|
ShippingDetails string `json:"shipping_details,omitempty"`
|
||||||
|
TitleMandatory bool `json:"title_mandatory,omitempty"`
|
||||||
|
AdditionalCheckbox *AdditionalCheckBox `json:"additional_checkbox"`
|
||||||
|
RadiusBorder string `json:"radius_border,omitempty"`
|
||||||
|
ShowSubtotalDetail bool `json:"show_subtotal_detail,omitempty"`
|
||||||
|
RequireValidateCallbackSuccess bool `json:"require_validate_callback_success,omitempty"`
|
||||||
|
AllowGlobalBillingCountries bool `json:"allow_global_billing_countries,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdditionalCheckBox struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
Checked bool `json:"checked"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckoutMerchantURLS struct {
|
||||||
|
// URL of merchant terms and conditions. Should be different than checkout, confirmation and push URLs.
|
||||||
|
// (max 2000 characters)
|
||||||
|
Terms string `json:"terms"`
|
||||||
|
|
||||||
|
// URL of merchant checkout page. Should be different than terms, confirmation and push URLs.
|
||||||
|
// (max 2000 characters)
|
||||||
|
Checkout string `json:"checkout"`
|
||||||
|
|
||||||
|
// URL of merchant confirmation page. Should be different than checkout and confirmation URLs.
|
||||||
|
// (max 2000 characters)
|
||||||
|
Confirmation string `json:"confirmation"`
|
||||||
|
|
||||||
|
// URL that will be requested when an order is completed. Should be different than checkout and
|
||||||
|
// confirmation URLs. (max 2000 characters)
|
||||||
|
Push string `json:"push"`
|
||||||
|
// URL that will be requested for final merchant validation. (must be https, max 2000 characters)
|
||||||
|
Validation string `json:"validation,omitempty"`
|
||||||
|
|
||||||
|
// URL for shipping option update. (must be https, max 2000 characters)
|
||||||
|
ShippingOptionUpdate string `json:"shipping_option_update,omitempty"`
|
||||||
|
|
||||||
|
// URL for shipping, tax and purchase currency updates. Will be called on address changes.
|
||||||
|
// (must be https, max 2000 characters)
|
||||||
|
AddressUpdate string `json:"address_update,omitempty"`
|
||||||
|
|
||||||
|
// URL for notifications on pending orders. (max 2000 characters)
|
||||||
|
Notification string `json:"notification,omitempty"`
|
||||||
|
|
||||||
|
// URL for shipping, tax and purchase currency updates. Will be called on purchase country changes.
|
||||||
|
// (must be https, max 2000 characters)
|
||||||
|
CountryChange string `json:"country_change,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckoutCustomer struct {
|
||||||
|
// DateOfBirth in string representation 2006-01-02
|
||||||
|
DateOfBirth string `json:"date_of_birth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address type define the address object (json serializable) being used for the API to represent billing &
|
||||||
|
// shipping addresses
|
||||||
|
Address struct {
|
||||||
|
GivenName string `json:"given_name,omitempty"`
|
||||||
|
FamilyName string `json:"family_name,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
StreetAddress string `json:"street_address,omitempty"`
|
||||||
|
StreetAddress2 string `json:"street_address2,omitempty"`
|
||||||
|
PostalCode string `json:"postal_code,omitempty"`
|
||||||
|
City string `json:"city,omitempty"`
|
||||||
|
Region string `json:"region,omitempty"`
|
||||||
|
Phone string `json:"phone,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Line struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Reference string `json:"reference,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
QuantityUnit string `json:"quantity_unit,omitempty"`
|
||||||
|
UnitPrice int `json:"unit_price"`
|
||||||
|
TaxRate int `json:"tax_rate"`
|
||||||
|
TotalAmount int `json:"total_amount"`
|
||||||
|
TotalDiscountAmount int `json:"total_discount_amount,omitempty"`
|
||||||
|
TotalTaxAmount int `json:"total_tax_amount"`
|
||||||
|
MerchantData string `json:"merchant_data,omitempty"`
|
||||||
|
ProductURL string `json:"product_url,omitempty"`
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
}
|
||||||
|
)
|
||||||
240
main.go
240
main.go
@@ -1,15 +1,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
"net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
messages "git.tornberg.me/go-cart-actor/proto"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
@@ -91,6 +94,8 @@ func (a *App) HandleSave(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var podIp = os.Getenv("POD_IP")
|
var podIp = os.Getenv("POD_IP")
|
||||||
var name = os.Getenv("POD_NAME")
|
var name = os.Getenv("POD_NAME")
|
||||||
|
var amqpUrl = os.Getenv("AMQP_URL")
|
||||||
|
var KlarnaInstance = NewKlarnaClient(KlarnaPlaygroundUrl, os.Getenv("KLARNA_API_USERNAME"), os.Getenv("KLARNA_API_PASSWORD"))
|
||||||
|
|
||||||
func GetDiscovery() Discovery {
|
func GetDiscovery() Discovery {
|
||||||
if podIp == "" {
|
if podIp == "" {
|
||||||
@@ -109,8 +114,47 @@ func GetDiscovery() Discovery {
|
|||||||
return NewK8sDiscovery(client)
|
return NewK8sDiscovery(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tpl = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>s10r testing - checkout</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
%s
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
func getCountryFromHost(host string) string {
|
||||||
|
if strings.Contains(strings.ToLower(host), "-no") {
|
||||||
|
return "no"
|
||||||
|
}
|
||||||
|
return "se"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCheckoutOrder(host string, cartId CartId) *messages.CreateCheckoutOrder {
|
||||||
|
baseUrl := fmt.Sprintf("https://%s", host)
|
||||||
|
cartBaseUrl := os.Getenv("CART_BASE_URL")
|
||||||
|
if cartBaseUrl == "" {
|
||||||
|
cartBaseUrl = "https://cart.tornberg.me"
|
||||||
|
}
|
||||||
|
country := getCountryFromHost(host)
|
||||||
|
|
||||||
|
return &messages.CreateCheckoutOrder{
|
||||||
|
Terms: fmt.Sprintf("%s/terms", baseUrl),
|
||||||
|
Checkout: fmt.Sprintf("%s/checkout?order_id={checkout.order.id}", baseUrl),
|
||||||
|
Confirmation: fmt.Sprintf("%s/confirmation/{checkout.order.id}", baseUrl),
|
||||||
|
Validation: fmt.Sprintf("%s/validation", cartBaseUrl),
|
||||||
|
Push: fmt.Sprintf("%s/push?order_id={checkout.order.id}", cartBaseUrl),
|
||||||
|
Country: country,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create a new instance of the server
|
|
||||||
storage, err := NewDiskStorage(fmt.Sprintf("data/%s_state.gob", name))
|
storage, err := NewDiskStorage(fmt.Sprintf("data/%s_state.gob", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error loading state: %v\n", err)
|
log.Printf("Error loading state: %v\n", err)
|
||||||
@@ -125,30 +169,35 @@ func main() {
|
|||||||
log.Fatalf("Error creating synced pool: %v\n", err)
|
log.Fatalf("Error creating synced pool: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hg, err := NewGrainHandler(app.pool, ":1337")
|
// Start unified gRPC server (CartActor + ControlPlane) replacing legacy RPC server on :1337
|
||||||
|
// TODO: Remove any remaining legacy RPC server references and deprecated frame-based code after full gRPC migration is validated.
|
||||||
|
grpcSrv, err := StartGRPCServer(":1337", app.pool, syncedPool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error creating handler: %v\n", err)
|
log.Fatalf("Error starting gRPC server: %v\n", err)
|
||||||
}
|
}
|
||||||
|
defer grpcSrv.GracefulStop()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for range time.Tick(time.Minute * 10) {
|
for range time.Tick(time.Minute * 10) {
|
||||||
|
|
||||||
err := app.Save()
|
err := app.Save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error saving: %v\n", err)
|
log.Printf("Error saving: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
orderHandler := &AmqpOrderHandler{
|
||||||
|
Url: amqpUrl,
|
||||||
|
}
|
||||||
|
|
||||||
syncedServer := NewPoolServer(syncedPool, fmt.Sprintf("%s, %s", name, podIp))
|
syncedServer := NewPoolServer(syncedPool, fmt.Sprintf("%s, %s", name, podIp))
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("/api/", http.StripPrefix("/api", syncedServer.Serve()))
|
mux.Handle("/cart/", http.StripPrefix("/cart", syncedServer.Serve()))
|
||||||
// only for local
|
// only for local
|
||||||
// mux.HandleFunc("GET /add/remote/{host}", func(w http.ResponseWriter, r *http.Request) {
|
// mux.HandleFunc("GET /add/remote/{host}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// syncedPool.AddRemote(r.PathValue("host"))
|
// syncedPool.AddRemote(r.PathValue("host"))
|
||||||
// })
|
// })
|
||||||
// mux.HandleFunc("GET /save", app.HandleSave)
|
// mux.HandleFunc("GET /save", app.HandleSave)
|
||||||
|
//mux.HandleFunc("/", app.RewritePath)
|
||||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||||
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
@@ -156,17 +205,21 @@ func main() {
|
|||||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
mux.Handle("/metrics", promhttp.Handler())
|
||||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !hg.IsHealthy() {
|
// Grain pool health: simple capacity check (mirrors previous GrainHandler.IsHealthy)
|
||||||
|
app.pool.mu.RLock()
|
||||||
|
grainCount := len(app.pool.grains)
|
||||||
|
capacity := app.pool.PoolSize
|
||||||
|
app.pool.mu.RUnlock()
|
||||||
|
if grainCount >= capacity {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte("handler not healthy"))
|
w.Write([]byte("grain pool at capacity"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !syncedPool.IsHealthy() {
|
if !syncedPool.IsHealthy() {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
w.Write([]byte("pool not healthy"))
|
w.Write([]byte("control plane not healthy"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
})
|
})
|
||||||
@@ -178,8 +231,142 @@ func main() {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/checkout", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
orderId := r.URL.Query().Get("order_id")
|
||||||
|
order := &CheckoutOrder{}
|
||||||
|
|
||||||
|
if orderId == "" {
|
||||||
|
cookie, err := r.Cookie("cartid")
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cookie.Value == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("no cart id to checkout is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cartId := ToCartId(cookie.Value)
|
||||||
|
reply, err := syncedServer.pool.Process(cartId, Message{
|
||||||
|
Type: CreateCheckoutOrderType,
|
||||||
|
Content: getCheckoutOrder(r.Host, cartId),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(reply.Payload, &order)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prevOrder, err := KlarnaInstance.GetOrder(orderId)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
order = prevOrder
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
w.Write([]byte(fmt.Sprintf(tpl, order.HTMLSnippet)))
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/confirmation/{order_id}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
orderId := r.PathValue("order_id")
|
||||||
|
order, err := KlarnaInstance.GetOrder(orderId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
if order.Status == "checkout_complete" {
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "cartid",
|
||||||
|
Value: "",
|
||||||
|
Path: "/",
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
Expires: time.Unix(0, 0),
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(fmt.Sprintf(tpl, order.HTMLSnippet)))
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
order := &CheckoutOrder{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(order)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
log.Printf("Klarna order validation: %s", order.ID)
|
||||||
|
//err = confirmOrder(order, orderHandler)
|
||||||
|
//if err != nil {
|
||||||
|
// log.Printf("Error validating order: %v\n", err)
|
||||||
|
// w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//err = triggerOrderCompleted(err, syncedServer, order)
|
||||||
|
//if err != nil {
|
||||||
|
// log.Printf("Error processing cart message: %v\n", err)
|
||||||
|
// w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
mux.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Print(r.Body)
|
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orderId := r.URL.Query().Get("order_id")
|
||||||
|
log.Printf("Order confirmation push: %s", orderId)
|
||||||
|
|
||||||
|
order, err := KlarnaInstance.GetOrder(orderId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error creating request: %v\n", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = confirmOrder(order, orderHandler)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error confirming order: %v\n", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = triggerOrderCompleted(err, syncedServer, order)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error processing cart message: %v\n", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = KlarnaInstance.AcknowledgeOrder(orderId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error acknowledging order: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
@@ -193,11 +380,42 @@ func main() {
|
|||||||
go func() {
|
go func() {
|
||||||
sig := <-sigs
|
sig := <-sigs
|
||||||
fmt.Println("Shutting down due to signal:", sig)
|
fmt.Println("Shutting down due to signal:", sig)
|
||||||
|
go syncedPool.Close()
|
||||||
app.Save()
|
app.Save()
|
||||||
done <- true
|
done <- true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
log.Print("Server started at port 8080")
|
||||||
go http.ListenAndServe(":8080", mux)
|
go http.ListenAndServe(":8080", mux)
|
||||||
<-done
|
<-done
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func triggerOrderCompleted(err error, syncedServer *PoolServer, order *CheckoutOrder) error {
|
||||||
|
_, err = syncedServer.pool.Process(ToCartId(order.MerchantReference1), Message{
|
||||||
|
Type: OrderCompletedType,
|
||||||
|
Content: &messages.OrderCreated{
|
||||||
|
OrderId: order.ID,
|
||||||
|
Status: order.Status,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func confirmOrder(order *CheckoutOrder, orderHandler *AmqpOrderHandler) error {
|
||||||
|
orderToSend, err := json.Marshal(order)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = orderHandler.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer orderHandler.Close()
|
||||||
|
err = orderHandler.OrderCompleted(orderToSend)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,18 +9,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var Handlers = map[uint16]MessageHandler{
|
var Handlers = map[uint16]MessageHandler{
|
||||||
AddRequestType: &AddRequestHandler{},
|
AddRequestType: &AddRequestHandler{},
|
||||||
AddItemType: &AddItemHandler{},
|
AddItemType: &AddItemHandler{},
|
||||||
ChangeQuantityType: &ChangeQuantityHandler{},
|
ChangeQuantityType: &ChangeQuantityHandler{},
|
||||||
SetDeliveryType: &SetDeliveryHandler{},
|
SetDeliveryType: &SetDeliveryHandler{},
|
||||||
RemoveItemType: &RemoveItemHandler{},
|
RemoveItemType: &RemoveItemHandler{},
|
||||||
RemoveDeliveryType: &RemoveDeliveryHandler{},
|
RemoveDeliveryType: &RemoveDeliveryHandler{},
|
||||||
|
CreateCheckoutOrderType: &CheckoutHandler{},
|
||||||
|
SetCartItemsType: &SetCartItemsHandler{},
|
||||||
|
OrderCompletedType: &OrderCompletedHandler{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMessageHandler(t uint16) (MessageHandler, error) {
|
func GetMessageHandler(t uint16) (MessageHandler, error) {
|
||||||
h, ok := Handlers[t]
|
h, ok := Handlers[t]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no handler for type %d", t)
|
return nil, fmt.Errorf("no handler for message type %d", t)
|
||||||
}
|
}
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
@@ -34,6 +37,37 @@ type TypedMessageHandler struct {
|
|||||||
Type uint16
|
Type uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SetCartItemsHandler struct {
|
||||||
|
TypedMessageHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SetCartItemsHandler) Write(m *Message, w io.Writer) error {
|
||||||
|
messageBytes, err := proto.Marshal(m.Content.(*messages.SetCartRequest))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.Write(messageBytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SetCartItemsHandler) Read(data []byte) (interface{}, error) {
|
||||||
|
msg := &messages.SetCartRequest{}
|
||||||
|
|
||||||
|
err := proto.Unmarshal(data, msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SetCartItemsHandler) Is(m *Message) bool {
|
||||||
|
if m.Type != AddRequestType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := m.Content.(*messages.SetCartRequest)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
type AddRequestHandler struct {
|
type AddRequestHandler struct {
|
||||||
TypedMessageHandler
|
TypedMessageHandler
|
||||||
}
|
}
|
||||||
@@ -250,3 +284,32 @@ func (h *CheckoutHandler) Is(m *Message) bool {
|
|||||||
_, ok := m.Content.(*messages.CreateCheckoutOrder)
|
_, ok := m.Content.(*messages.CreateCheckoutOrder)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OrderCompletedHandler struct {
|
||||||
|
TypedMessageHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrderCompletedHandler) Write(m *Message, w io.Writer) error {
|
||||||
|
messageBytes, err := proto.Marshal(m.Content.(*messages.OrderCreated))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.Write(messageBytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (h *OrderCompletedHandler) Read(data []byte) (interface{}, error) {
|
||||||
|
msg := &messages.OrderCreated{}
|
||||||
|
|
||||||
|
err := proto.Unmarshal(data, msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
func (h *OrderCompletedHandler) Is(m *Message) bool {
|
||||||
|
if m.Type != OrderCompletedType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := m.Content.(*messages.OrderCreated)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
messages "git.tornberg.me/go-cart-actor/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddRequest(t *testing.T) {
|
|
||||||
h, err := GetMessageHandler(AddRequestType)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error getting message handler: %v\n", err)
|
|
||||||
}
|
|
||||||
if h == nil {
|
|
||||||
t.Errorf("Expected message handler, got nil\n")
|
|
||||||
}
|
|
||||||
message := Message{
|
|
||||||
Type: AddRequestType,
|
|
||||||
Content: &messages.AddRequest{
|
|
||||||
Quantity: 2,
|
|
||||||
Sku: "123",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
err = h.Write(&message, &b)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error writing message: %v\n", err)
|
|
||||||
}
|
|
||||||
result, err := h.Read(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error reading message: %v\n", err)
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
t.Errorf("Expected result, got nil\n")
|
|
||||||
}
|
|
||||||
r, ok := result.(*messages.AddRequest)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("Expected AddRequest, got %T\n", result)
|
|
||||||
}
|
|
||||||
if r.Quantity != 2 {
|
|
||||||
t.Errorf("Expected quantity 2, got %d\n", r.Quantity)
|
|
||||||
}
|
|
||||||
if r.Sku != "123" {
|
|
||||||
t.Errorf("Expected sku '123', got %s\n", r.Sku)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestItemRequest(t *testing.T) {
|
|
||||||
h, err := GetMessageHandler(AddItemType)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error getting message handler: %v\n", err)
|
|
||||||
}
|
|
||||||
if h == nil {
|
|
||||||
t.Errorf("Expected message handler, got nil\n")
|
|
||||||
}
|
|
||||||
message := Message{
|
|
||||||
Type: AddItemType,
|
|
||||||
Content: &messages.AddItem{
|
|
||||||
Quantity: 2,
|
|
||||||
Sku: "123",
|
|
||||||
Price: 100,
|
|
||||||
Name: "Test item",
|
|
||||||
Image: "test.jpg",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
err = h.Write(&message, &b)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error writing message: %v\n", err)
|
|
||||||
}
|
|
||||||
result, err := h.Read(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error reading message: %v\n", err)
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
t.Errorf("Expected result, got nil\n")
|
|
||||||
}
|
|
||||||
var r *messages.AddItem
|
|
||||||
ok := h.Is(&message)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("Expected AddRequest, got %T\n", result)
|
|
||||||
}
|
|
||||||
if r.Quantity != 2 {
|
|
||||||
t.Errorf("Expected quantity 2, got %d\n", r.Quantity)
|
|
||||||
}
|
|
||||||
if r.Sku != "123" {
|
|
||||||
t.Errorf("Expected sku '123', got %s\n", r.Sku)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetDeliveryMssage(t *testing.T) {
|
|
||||||
h, err := GetMessageHandler(SetDeliveryType)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error getting message handler: %v\n", err)
|
|
||||||
}
|
|
||||||
if h == nil {
|
|
||||||
t.Errorf("Expected message handler, got nil\n")
|
|
||||||
}
|
|
||||||
message := Message{
|
|
||||||
Type: SetDeliveryType,
|
|
||||||
Content: &messages.SetDelivery{
|
|
||||||
Provider: "test",
|
|
||||||
Items: []int64{1, 2},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
err = h.Write(&message, &b)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error writing message: %v\n", err)
|
|
||||||
}
|
|
||||||
result, err := h.Read(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error reading message: %v\n", err)
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
t.Errorf("Expected result, got nil\n")
|
|
||||||
}
|
|
||||||
r, ok := result.(*messages.SetDelivery)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("Expected AddRequest, got %T\n", result)
|
|
||||||
}
|
|
||||||
if len(r.Items) != 2 {
|
|
||||||
t.Errorf("Expected 2 items, got %d\n", len(r.Items))
|
|
||||||
}
|
|
||||||
if r.Provider != "test" {
|
|
||||||
t.Errorf("Expected provider 'test', got %s\n", r.Provider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,4 +10,6 @@ const (
|
|||||||
SetDeliveryType = 7
|
SetDeliveryType = 7
|
||||||
SetPickupPointType = 8
|
SetPickupPointType = 8
|
||||||
CreateCheckoutOrderType = 9
|
CreateCheckoutOrderType = 9
|
||||||
|
SetCartItemsType = 10
|
||||||
|
OrderCompletedType = 11
|
||||||
)
|
)
|
||||||
|
|||||||
89
packet.go
89
packet.go
@@ -1,89 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
const (
|
|
||||||
RemoteGetState = FrameType(0x01)
|
|
||||||
RemoteHandleMutation = FrameType(0x02)
|
|
||||||
ResponseBody = FrameType(0x03)
|
|
||||||
RemoteGetStateReply = FrameType(0x04)
|
|
||||||
RemoteHandleMutationReply = FrameType(0x05)
|
|
||||||
RemoteCreateOrderReply = FrameType(0x06)
|
|
||||||
)
|
|
||||||
|
|
||||||
// type CartPacket struct {
|
|
||||||
// Version PackageVersion
|
|
||||||
// MessageType CartMessage
|
|
||||||
// DataLength uint32
|
|
||||||
// StatusCode uint32
|
|
||||||
// Id CartId
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type Packet struct {
|
|
||||||
// Version PackageVersion
|
|
||||||
// MessageType PoolMessage
|
|
||||||
// DataLength uint32
|
|
||||||
// StatusCode uint32
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var headerData = make([]byte, 4)
|
|
||||||
|
|
||||||
// func matchHeader(conn io.Reader) error {
|
|
||||||
|
|
||||||
// pos := 0
|
|
||||||
// for pos < 4 {
|
|
||||||
|
|
||||||
// l, err := conn.Read(headerData)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// for i := 0; i < l; i++ {
|
|
||||||
// if headerData[i] == header[pos] {
|
|
||||||
// pos++
|
|
||||||
// if pos == 4 {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// pos = 0
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func ReadPacket(conn io.Reader, packet *Packet) error {
|
|
||||||
// err := matchHeader(conn)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// return binary.Read(conn, binary.LittleEndian, packet)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func ReadCartPacket(conn io.Reader, packet *CartPacket) error {
|
|
||||||
// err := matchHeader(conn)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// return binary.Read(conn, binary.LittleEndian, packet)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func GetPacketData(conn io.Reader, len uint32) ([]byte, error) {
|
|
||||||
// if len == 0 {
|
|
||||||
// return []byte{}, nil
|
|
||||||
// }
|
|
||||||
// data := make([]byte, len)
|
|
||||||
// _, err := conn.Read(data)
|
|
||||||
// return data, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func ReceivePacket(conn io.Reader) (uint32, []byte, error) {
|
|
||||||
// var packet Packet
|
|
||||||
// err := ReadPacket(conn, &packet)
|
|
||||||
// if err != nil {
|
|
||||||
// return 0, nil, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// data, err := GetPacketData(conn, packet.DataLength)
|
|
||||||
// if err != nil {
|
|
||||||
// return 0, nil, err
|
|
||||||
// }
|
|
||||||
// return packet.MessageType, data, nil
|
|
||||||
// }
|
|
||||||
228
pool-server.go
228
pool-server.go
@@ -1,12 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
messages "git.tornberg.me/go-cart-actor/proto"
|
messages "git.tornberg.me/go-cart-actor/proto"
|
||||||
)
|
)
|
||||||
@@ -23,20 +24,18 @@ func NewPoolServer(pool GrainPool, pod_name string) *PoolServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleGet(w http.ResponseWriter, r *http.Request) error {
|
func (s *PoolServer) HandleGet(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
id := r.PathValue("id")
|
data, err := s.pool.Get(id)
|
||||||
|
|
||||||
data, err := s.pool.Get(ToCartId(id))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.WriteResult(w, data)
|
return s.WriteResult(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleAddSku(w http.ResponseWriter, r *http.Request) error {
|
func (s *PoolServer) HandleAddSku(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
id := r.PathValue("id")
|
|
||||||
sku := r.PathValue("sku")
|
sku := r.PathValue("sku")
|
||||||
data, err := s.pool.Process(ToCartId(id), Message{
|
data, err := s.pool.Process(id, Message{
|
||||||
Type: AddRequestType,
|
Type: AddRequestType,
|
||||||
Content: &messages.AddRequest{Sku: sku, Quantity: 1},
|
Content: &messages.AddRequest{Sku: sku, Quantity: 1},
|
||||||
})
|
})
|
||||||
@@ -59,8 +58,9 @@ func ErrorHandler(fn func(w http.ResponseWriter, r *http.Request) error) func(w
|
|||||||
|
|
||||||
func (s *PoolServer) WriteResult(w http.ResponseWriter, result *FrameWithPayload) error {
|
func (s *PoolServer) WriteResult(w http.ResponseWriter, result *FrameWithPayload) error {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("X-Pod-Name", s.pod_name)
|
w.Header().Set("X-Pod-Name", s.pod_name)
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "https://tornberg.me")
|
|
||||||
if result.StatusCode != 200 {
|
if result.StatusCode != 200 {
|
||||||
log.Printf("Call error: %d\n", result.StatusCode)
|
log.Printf("Call error: %d\n", result.StatusCode)
|
||||||
if result.StatusCode >= 200 && result.StatusCode < 600 {
|
if result.StatusCode >= 200 && result.StatusCode < 600 {
|
||||||
@@ -76,14 +76,14 @@ func (s *PoolServer) WriteResult(w http.ResponseWriter, result *FrameWithPayload
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleDeleteItem(w http.ResponseWriter, r *http.Request) error {
|
func (s *PoolServer) HandleDeleteItem(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
id := r.PathValue("id")
|
|
||||||
itemIdString := r.PathValue("itemId")
|
itemIdString := r.PathValue("itemId")
|
||||||
itemId, err := strconv.Atoi(itemIdString)
|
itemId, err := strconv.Atoi(itemIdString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data, err := s.pool.Process(ToCartId(id), Message{
|
data, err := s.pool.Process(id, Message{
|
||||||
Type: RemoveItemType,
|
Type: RemoveItemType,
|
||||||
Content: &messages.RemoveItem{Id: int64(itemId)},
|
Content: &messages.RemoveItem{Id: int64(itemId)},
|
||||||
})
|
})
|
||||||
@@ -94,22 +94,24 @@ func (s *PoolServer) HandleDeleteItem(w http.ResponseWriter, r *http.Request) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SetDelivery struct {
|
type SetDelivery struct {
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
Items []int64 `json:"items"`
|
Items []int64 `json:"items"`
|
||||||
|
PickupPoint *messages.PickupPoint `json:"pickupPoint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleSetDelivery(w http.ResponseWriter, r *http.Request) error {
|
func (s *PoolServer) HandleSetDelivery(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
id := r.PathValue("id")
|
|
||||||
delivery := SetDelivery{}
|
delivery := SetDelivery{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&delivery)
|
err := json.NewDecoder(r.Body).Decode(&delivery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data, err := s.pool.Process(ToCartId(id), Message{
|
data, err := s.pool.Process(id, Message{
|
||||||
Type: SetDeliveryType,
|
Type: SetDeliveryType,
|
||||||
Content: &messages.SetDelivery{
|
Content: &messages.SetDelivery{
|
||||||
Provider: delivery.Provider,
|
Provider: delivery.Provider,
|
||||||
Items: delivery.Items,
|
Items: delivery.Items,
|
||||||
|
PickupPoint: delivery.PickupPoint,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,8 +120,8 @@ func (s *PoolServer) HandleSetDelivery(w http.ResponseWriter, r *http.Request) e
|
|||||||
return s.WriteResult(w, data)
|
return s.WriteResult(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleSetPickupPoint(w http.ResponseWriter, r *http.Request) error {
|
func (s *PoolServer) HandleSetPickupPoint(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
id := r.PathValue("id")
|
|
||||||
deliveryIdString := r.PathValue("deliveryId")
|
deliveryIdString := r.PathValue("deliveryId")
|
||||||
deliveryId, err := strconv.Atoi(deliveryIdString)
|
deliveryId, err := strconv.Atoi(deliveryIdString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -130,7 +132,7 @@ func (s *PoolServer) HandleSetPickupPoint(w http.ResponseWriter, r *http.Request
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reply, err := s.pool.Process(ToCartId(id), Message{
|
reply, err := s.pool.Process(id, Message{
|
||||||
Type: SetPickupPointType,
|
Type: SetPickupPointType,
|
||||||
Content: &messages.SetPickupPoint{
|
Content: &messages.SetPickupPoint{
|
||||||
DeliveryId: int64(deliveryId),
|
DeliveryId: int64(deliveryId),
|
||||||
@@ -148,14 +150,14 @@ func (s *PoolServer) HandleSetPickupPoint(w http.ResponseWriter, r *http.Request
|
|||||||
return s.WriteResult(w, reply)
|
return s.WriteResult(w, reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleRemoveDelivery(w http.ResponseWriter, r *http.Request) error {
|
func (s *PoolServer) HandleRemoveDelivery(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
id := r.PathValue("id")
|
|
||||||
deliveryIdString := r.PathValue("deliveryId")
|
deliveryIdString := r.PathValue("deliveryId")
|
||||||
deliveryId, err := strconv.Atoi(deliveryIdString)
|
deliveryId, err := strconv.Atoi(deliveryIdString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reply, err := s.pool.Process(ToCartId(id), Message{
|
reply, err := s.pool.Process(id, Message{
|
||||||
Type: RemoveDeliveryType,
|
Type: RemoveDeliveryType,
|
||||||
Content: &messages.RemoveDelivery{Id: int64(deliveryId)},
|
Content: &messages.RemoveDelivery{Id: int64(deliveryId)},
|
||||||
})
|
})
|
||||||
@@ -165,15 +167,13 @@ func (s *PoolServer) HandleRemoveDelivery(w http.ResponseWriter, r *http.Request
|
|||||||
return s.WriteResult(w, reply)
|
return s.WriteResult(w, reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleQuantityChange(w http.ResponseWriter, r *http.Request) error {
|
func (s *PoolServer) HandleQuantityChange(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
id := r.PathValue("id")
|
|
||||||
|
|
||||||
changeQuantity := messages.ChangeQuantity{}
|
changeQuantity := messages.ChangeQuantity{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&changeQuantity)
|
err := json.NewDecoder(r.Body).Decode(&changeQuantity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reply, err := s.pool.Process(ToCartId(id), Message{
|
reply, err := s.pool.Process(id, Message{
|
||||||
Type: ChangeQuantityType,
|
Type: ChangeQuantityType,
|
||||||
Content: &changeQuantity,
|
Content: &changeQuantity,
|
||||||
})
|
})
|
||||||
@@ -183,15 +183,29 @@ func (s *PoolServer) HandleQuantityChange(w http.ResponseWriter, r *http.Request
|
|||||||
return s.WriteResult(w, reply)
|
return s.WriteResult(w, reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PoolServer) HandleAddRequest(w http.ResponseWriter, r *http.Request) error {
|
func (s *PoolServer) HandleSetCartItems(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
id := r.PathValue("id")
|
setCartItems := messages.SetCartRequest{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&setCartItems)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reply, err := s.pool.Process(id, Message{
|
||||||
|
Type: SetCartItemsType,
|
||||||
|
Content: &setCartItems,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.WriteResult(w, reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PoolServer) HandleAddRequest(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
addRequest := messages.AddRequest{}
|
addRequest := messages.AddRequest{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&addRequest)
|
err := json.NewDecoder(r.Body).Decode(&addRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reply, err := s.pool.Process(ToCartId(id), Message{
|
reply, err := s.pool.Process(id, Message{
|
||||||
Type: AddRequestType,
|
Type: AddRequestType,
|
||||||
Content: &addRequest,
|
Content: &addRequest,
|
||||||
})
|
})
|
||||||
@@ -201,21 +215,34 @@ func (s *PoolServer) HandleAddRequest(w http.ResponseWriter, r *http.Request) er
|
|||||||
return s.WriteResult(w, reply)
|
return s.WriteResult(w, reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func (s *PoolServer) HandleConfirmation(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
APIUsername = os.Getenv("KLARNA_API_USERNAME")
|
orderId := r.PathValue("orderId")
|
||||||
APIPassword = os.Getenv("KLARNA_API_PASSWORD")
|
if orderId == "" {
|
||||||
)
|
return fmt.Errorf("orderId is empty")
|
||||||
|
}
|
||||||
|
order, err := KlarnaInstance.GetOrder(orderId)
|
||||||
|
|
||||||
func (s *PoolServer) HandleCheckout(w http.ResponseWriter, r *http.Request) error {
|
if err != nil {
|
||||||
id := r.PathValue("id")
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
reply, err := s.pool.Process(ToCartId(id), Message{
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("X-Pod-Name", s.pod_name)
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return json.NewEncoder(w).Encode(order)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PoolServer) HandleCheckout(w http.ResponseWriter, r *http.Request, id CartId) error {
|
||||||
|
|
||||||
|
reply, err := s.pool.Process(id, Message{
|
||||||
Type: CreateCheckoutOrderType,
|
Type: CreateCheckoutOrderType,
|
||||||
Content: &messages.CreateCheckoutOrder{
|
Content: &messages.CreateCheckoutOrder{
|
||||||
Terms: "https://tornberg.me/terms",
|
Terms: "https://slask-finder.tornberg.me/terms",
|
||||||
Checkout: "https://tornberg.me/checkout",
|
Checkout: "https://slask-finder.tornberg.me/checkout?order_id={checkout.order.id}",
|
||||||
Confirmation: "https://tornberg.me/confirmation",
|
Confirmation: "https://slask-finder.tornberg.me/confirmation/{checkout.order.id}",
|
||||||
Push: "https://cart.tornberg.me/push",
|
Push: "https://cart.tornberg.me/push?order_id={checkout.order.id}",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -225,41 +252,98 @@ func (s *PoolServer) HandleCheckout(w http.ResponseWriter, r *http.Request) erro
|
|||||||
return s.WriteResult(w, reply)
|
return s.WriteResult(w, reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "https://api.playground.klarna.com/checkout/v3/orders", bytes.NewReader(reply.Payload))
|
// w.Header().Set("Content-Type", "application/json")
|
||||||
if err != nil {
|
// w.Header().Set("X-Pod-Name", s.pod_name)
|
||||||
return err
|
// w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
// w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
// w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
return s.WriteResult(w, reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCartId() CartId {
|
||||||
|
id := time.Now().UnixNano() + rand.Int63()
|
||||||
|
|
||||||
|
return ToCartId(fmt.Sprintf("%d", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CookieCartIdHandler(fn func(w http.ResponseWriter, r *http.Request, cartId CartId) error) func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
var cartId CartId
|
||||||
|
cartIdCookie := r.CookiesNamed("cartid")
|
||||||
|
if cartIdCookie == nil || len(cartIdCookie) == 0 {
|
||||||
|
cartId = NewCartId()
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "cartid",
|
||||||
|
Value: cartId.String(),
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
Path: "/",
|
||||||
|
Expires: time.Now().AddDate(0, 0, 14),
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cartId = ToCartId(cartIdCookie[0].Value)
|
||||||
|
}
|
||||||
|
return fn(w, r, cartId)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
func (s *PoolServer) RemoveCartCookie(w http.ResponseWriter, r *http.Request, cartId CartId) error {
|
||||||
req.SetBasicAuth(APIUsername, APIPassword)
|
cartId = NewCartId()
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
res, err := http.DefaultClient.Do(req)
|
Name: "cartid",
|
||||||
if nil != err {
|
Value: cartId.String(),
|
||||||
return err
|
Path: "/",
|
||||||
}
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
buf := new(bytes.Buffer)
|
Expires: time.Unix(0, 0),
|
||||||
buf.ReadFrom(res.Body)
|
SameSite: http.SameSiteLaxMode,
|
||||||
w.Header().Set("Content-Type", "application/json")
|
})
|
||||||
w.Header().Set("X-Pod-Name", s.pod_name)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "https://tornberg.me")
|
|
||||||
w.WriteHeader(res.StatusCode)
|
|
||||||
|
|
||||||
w.Write(buf.Bytes())
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CartIdHandler(fn func(w http.ResponseWriter, r *http.Request, cartId CartId) error) func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
cartId := ToCartId(r.PathValue("id"))
|
||||||
|
return fn(w, r, cartId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PoolServer) Serve() *http.ServeMux {
|
func (s *PoolServer) Serve() *http.ServeMux {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("GET /{id}", ErrorHandler(s.HandleGet))
|
//mux.HandleFunc("/", s.RewritePath)
|
||||||
mux.HandleFunc("GET /{id}/add/{sku}", ErrorHandler(s.HandleAddSku))
|
mux.HandleFunc("OPTIONS /", func(w http.ResponseWriter, r *http.Request) {
|
||||||
mux.HandleFunc("POST /{id}", ErrorHandler(s.HandleAddRequest))
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
mux.HandleFunc("DELETE /{id}/{itemId}", ErrorHandler(s.HandleDeleteItem))
|
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE")
|
||||||
mux.HandleFunc("PUT /{id}", ErrorHandler(s.HandleQuantityChange))
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||||
mux.HandleFunc("POST /{id}/delivery", ErrorHandler(s.HandleSetDelivery))
|
w.WriteHeader(http.StatusOK)
|
||||||
mux.HandleFunc("DELETE /{id}/delivery/{deliveryId}", ErrorHandler(s.HandleRemoveDelivery))
|
})
|
||||||
mux.HandleFunc("PUT /{id}/delivery/{deliveryId}/pickupPoint", ErrorHandler(s.HandleSetPickupPoint))
|
|
||||||
mux.HandleFunc("GET /{id}/checkout", ErrorHandler(s.HandleCheckout))
|
mux.HandleFunc("GET /", ErrorHandler(CookieCartIdHandler(s.HandleGet)))
|
||||||
|
mux.HandleFunc("GET /add/{sku}", ErrorHandler(CookieCartIdHandler(s.HandleAddSku)))
|
||||||
|
mux.HandleFunc("POST /", ErrorHandler(CookieCartIdHandler(s.HandleAddRequest)))
|
||||||
|
mux.HandleFunc("POST /set", ErrorHandler(CookieCartIdHandler(s.HandleSetCartItems)))
|
||||||
|
mux.HandleFunc("DELETE /{itemId}", ErrorHandler(CookieCartIdHandler(s.HandleDeleteItem)))
|
||||||
|
mux.HandleFunc("PUT /", ErrorHandler(CookieCartIdHandler(s.HandleQuantityChange)))
|
||||||
|
mux.HandleFunc("DELETE /", ErrorHandler(CookieCartIdHandler(s.RemoveCartCookie)))
|
||||||
|
mux.HandleFunc("POST /delivery", ErrorHandler(CookieCartIdHandler(s.HandleSetDelivery)))
|
||||||
|
mux.HandleFunc("DELETE /delivery/{deliveryId}", ErrorHandler(CookieCartIdHandler(s.HandleRemoveDelivery)))
|
||||||
|
mux.HandleFunc("PUT /delivery/{deliveryId}/pickupPoint", ErrorHandler(CookieCartIdHandler(s.HandleSetPickupPoint)))
|
||||||
|
mux.HandleFunc("GET /checkout", ErrorHandler(CookieCartIdHandler(s.HandleCheckout)))
|
||||||
|
mux.HandleFunc("GET /confirmation/{orderId}", ErrorHandler(CookieCartIdHandler(s.HandleConfirmation)))
|
||||||
|
|
||||||
|
mux.HandleFunc("GET /byid/{id}", ErrorHandler(CartIdHandler(s.HandleGet)))
|
||||||
|
mux.HandleFunc("GET /byid/{id}/add/{sku}", ErrorHandler(CartIdHandler(s.HandleAddSku)))
|
||||||
|
mux.HandleFunc("POST /byid/{id}", ErrorHandler(CartIdHandler(s.HandleAddRequest)))
|
||||||
|
mux.HandleFunc("DELETE /byid/{id}/{itemId}", ErrorHandler(CartIdHandler(s.HandleDeleteItem)))
|
||||||
|
mux.HandleFunc("PUT /byid/{id}", ErrorHandler(CartIdHandler(s.HandleQuantityChange)))
|
||||||
|
mux.HandleFunc("POST /byid/{id}/delivery", ErrorHandler(CartIdHandler(s.HandleSetDelivery)))
|
||||||
|
mux.HandleFunc("DELETE /byid/{id}/delivery/{deliveryId}", ErrorHandler(CartIdHandler(s.HandleRemoveDelivery)))
|
||||||
|
mux.HandleFunc("PUT /byid/{id}/delivery/{deliveryId}/pickupPoint", ErrorHandler(CartIdHandler(s.HandleSetPickupPoint)))
|
||||||
|
mux.HandleFunc("GET /byid/{id}/checkout", ErrorHandler(CartIdHandler(s.HandleCheckout)))
|
||||||
|
mux.HandleFunc("GET /byid/{id}/confirmation", ErrorHandler(CartIdHandler(s.HandleConfirmation)))
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,75 +2,34 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matst80/slask-finder/pkg/index"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnergyRating struct {
|
// TODO make this configurable
|
||||||
Value string `json:"value,omitempty"`
|
func getBaseUrl(country string) string {
|
||||||
Min string `json:"min,omitempty"`
|
// if country == "se" {
|
||||||
Max string `json:"max,omitempty"`
|
// return "http://s10n-se:8080"
|
||||||
|
// }
|
||||||
|
if country == "no" {
|
||||||
|
return "http://s10n-no.s10n:8080"
|
||||||
|
}
|
||||||
|
if country == "se" {
|
||||||
|
return "http://s10n-se.s10n:8080"
|
||||||
|
}
|
||||||
|
return "http://localhost:8082"
|
||||||
}
|
}
|
||||||
|
|
||||||
type PriceTuple struct {
|
func FetchItem(sku string, country string) (*index.DataItem, error) {
|
||||||
IncVat int `json:"inc"`
|
baseUrl := getBaseUrl(country)
|
||||||
ExVat int `json:"exl"`
|
res, err := http.Get(fmt.Sprintf("%s/api/by-sku/%s", baseUrl, sku))
|
||||||
}
|
|
||||||
|
|
||||||
type OutletItem struct {
|
|
||||||
ArticleNumber string `json:"sku,omitempty"`
|
|
||||||
Price PriceTuple `json:"price,omitempty"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemProp struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
Disclaimer string `json:"disclaimer,omitempty"`
|
|
||||||
ReleaseDate string `json:"releaseDate,omitempty"`
|
|
||||||
SaleStatus string `json:"saleStatus"`
|
|
||||||
MarginPercent float64 `json:"mp,omitempty"`
|
|
||||||
PresaleDate string `json:"presaleDate,omitempty"`
|
|
||||||
Restock string `json:"restock,omitempty"`
|
|
||||||
AdvertisingText string `json:"advertisingText,omitempty"`
|
|
||||||
Img string `json:"img,omitempty"`
|
|
||||||
BadgeUrl string `json:"badgeUrl,omitempty"`
|
|
||||||
EnergyRating *EnergyRating `json:"energyRating,omitempty"`
|
|
||||||
BulletPoints string `json:"bp,omitempty"`
|
|
||||||
LastUpdate int64 `json:"lastUpdate,omitempty"`
|
|
||||||
Created int64 `json:"created,omitempty"`
|
|
||||||
Buyable bool `json:"buyable"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
BuyableInStore bool `json:"buyableInStore"`
|
|
||||||
BoxSize string `json:"boxSize,omitempty"`
|
|
||||||
CheapestBItem *OutletItem `json:"bItem,omitempty"`
|
|
||||||
AItem *OutletItem `json:"aItem,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseItem struct {
|
|
||||||
ItemProp
|
|
||||||
StockLevel string `json:"stockLevel,omitempty"`
|
|
||||||
Stock LocationStock `json:"stock"`
|
|
||||||
Id uint `json:"id"`
|
|
||||||
Sku string `json:"sku"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataItem struct {
|
|
||||||
*BaseItem
|
|
||||||
Fields map[uint]interface{} `json:"values"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocationStock []struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func FetchItem(sku string) (*DataItem, error) {
|
|
||||||
res, err := http.Get("https://slask-finder.tornberg.me/api/get/" + sku)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
var item DataItem
|
var item index.DataItem
|
||||||
err = json.NewDecoder(res.Body).Decode(&item)
|
err = json.NewDecoder(res.Body).Decode(&item)
|
||||||
return &item, err
|
return &item, err
|
||||||
}
|
}
|
||||||
|
|||||||
420
proto/cart_actor.pb.go
Normal file
420
proto/cart_actor.pb.go
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.10
|
||||||
|
// protoc v3.21.12
|
||||||
|
// source: cart_actor.proto
|
||||||
|
|
||||||
|
package messages
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// MutationType corresponds 1:1 with the legacy uint16 message type constants.
|
||||||
|
type MutationType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
MutationType_MUTATION_TYPE_UNSPECIFIED MutationType = 0
|
||||||
|
MutationType_MUTATION_ADD_REQUEST MutationType = 1
|
||||||
|
MutationType_MUTATION_ADD_ITEM MutationType = 2
|
||||||
|
// (3 was unused / reserved in legacy framing)
|
||||||
|
MutationType_MUTATION_REMOVE_ITEM MutationType = 4
|
||||||
|
MutationType_MUTATION_REMOVE_DELIVERY MutationType = 5
|
||||||
|
MutationType_MUTATION_CHANGE_QUANTITY MutationType = 6
|
||||||
|
MutationType_MUTATION_SET_DELIVERY MutationType = 7
|
||||||
|
MutationType_MUTATION_SET_PICKUP_POINT MutationType = 8
|
||||||
|
MutationType_MUTATION_CREATE_CHECKOUT_ORDER MutationType = 9
|
||||||
|
MutationType_MUTATION_SET_CART_ITEMS MutationType = 10
|
||||||
|
MutationType_MUTATION_ORDER_COMPLETED MutationType = 11
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for MutationType.
|
||||||
|
var (
|
||||||
|
MutationType_name = map[int32]string{
|
||||||
|
0: "MUTATION_TYPE_UNSPECIFIED",
|
||||||
|
1: "MUTATION_ADD_REQUEST",
|
||||||
|
2: "MUTATION_ADD_ITEM",
|
||||||
|
4: "MUTATION_REMOVE_ITEM",
|
||||||
|
5: "MUTATION_REMOVE_DELIVERY",
|
||||||
|
6: "MUTATION_CHANGE_QUANTITY",
|
||||||
|
7: "MUTATION_SET_DELIVERY",
|
||||||
|
8: "MUTATION_SET_PICKUP_POINT",
|
||||||
|
9: "MUTATION_CREATE_CHECKOUT_ORDER",
|
||||||
|
10: "MUTATION_SET_CART_ITEMS",
|
||||||
|
11: "MUTATION_ORDER_COMPLETED",
|
||||||
|
}
|
||||||
|
MutationType_value = map[string]int32{
|
||||||
|
"MUTATION_TYPE_UNSPECIFIED": 0,
|
||||||
|
"MUTATION_ADD_REQUEST": 1,
|
||||||
|
"MUTATION_ADD_ITEM": 2,
|
||||||
|
"MUTATION_REMOVE_ITEM": 4,
|
||||||
|
"MUTATION_REMOVE_DELIVERY": 5,
|
||||||
|
"MUTATION_CHANGE_QUANTITY": 6,
|
||||||
|
"MUTATION_SET_DELIVERY": 7,
|
||||||
|
"MUTATION_SET_PICKUP_POINT": 8,
|
||||||
|
"MUTATION_CREATE_CHECKOUT_ORDER": 9,
|
||||||
|
"MUTATION_SET_CART_ITEMS": 10,
|
||||||
|
"MUTATION_ORDER_COMPLETED": 11,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x MutationType) Enum() *MutationType {
|
||||||
|
p := new(MutationType)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x MutationType) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MutationType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_cart_actor_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MutationType) Type() protoreflect.EnumType {
|
||||||
|
return &file_cart_actor_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x MutationType) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use MutationType.Descriptor instead.
|
||||||
|
func (MutationType) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_cart_actor_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutationRequest is an envelope:
|
||||||
|
// - cart_id: string form of CartId (legacy 16-byte array truncated/padded).
|
||||||
|
// - type: mutation kind (see enum).
|
||||||
|
// - payload: serialized underlying proto message (AddRequest, AddItem, etc.).
|
||||||
|
// - client_timestamp: optional unix timestamp; server sets if zero.
|
||||||
|
type MutationRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
CartId string `protobuf:"bytes,1,opt,name=cart_id,json=cartId,proto3" json:"cart_id,omitempty"`
|
||||||
|
Type MutationType `protobuf:"varint,2,opt,name=type,proto3,enum=messages.MutationType" json:"type,omitempty"`
|
||||||
|
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||||
|
ClientTimestamp int64 `protobuf:"varint,4,opt,name=client_timestamp,json=clientTimestamp,proto3" json:"client_timestamp,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationRequest) Reset() {
|
||||||
|
*x = MutationRequest{}
|
||||||
|
mi := &file_cart_actor_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*MutationRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *MutationRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_cart_actor_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use MutationRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*MutationRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_cart_actor_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationRequest) GetCartId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.CartId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationRequest) GetType() MutationType {
|
||||||
|
if x != nil {
|
||||||
|
return x.Type
|
||||||
|
}
|
||||||
|
return MutationType_MUTATION_TYPE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationRequest) GetPayload() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Payload
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationRequest) GetClientTimestamp() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ClientTimestamp
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutationReply returns a status code (legacy semantics) plus a JSON payload
|
||||||
|
// representing the full cart state (or an error message if non-200).
|
||||||
|
type MutationReply struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
|
||||||
|
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // JSON cart state or error string
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationReply) Reset() {
|
||||||
|
*x = MutationReply{}
|
||||||
|
mi := &file_cart_actor_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationReply) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*MutationReply) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *MutationReply) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_cart_actor_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use MutationReply.ProtoReflect.Descriptor instead.
|
||||||
|
func (*MutationReply) Descriptor() ([]byte, []int) {
|
||||||
|
return file_cart_actor_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationReply) GetStatusCode() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.StatusCode
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MutationReply) GetPayload() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Payload
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateRequest fetches current cart state without mutation.
|
||||||
|
type StateRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
CartId string `protobuf:"bytes,1,opt,name=cart_id,json=cartId,proto3" json:"cart_id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StateRequest) Reset() {
|
||||||
|
*x = StateRequest{}
|
||||||
|
mi := &file_cart_actor_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StateRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StateRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StateRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_cart_actor_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StateRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StateRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_cart_actor_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StateRequest) GetCartId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.CartId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateReply mirrors MutationReply for consistency.
|
||||||
|
type StateReply struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
|
||||||
|
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // JSON cart state or error string
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StateReply) Reset() {
|
||||||
|
*x = StateReply{}
|
||||||
|
mi := &file_cart_actor_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StateReply) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StateReply) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StateReply) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_cart_actor_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StateReply.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StateReply) Descriptor() ([]byte, []int) {
|
||||||
|
return file_cart_actor_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StateReply) GetStatusCode() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.StatusCode
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StateReply) GetPayload() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Payload
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_cart_actor_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_cart_actor_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x10cart_actor.proto\x12\bmessages\"\x9b\x01\n" +
|
||||||
|
"\x0fMutationRequest\x12\x17\n" +
|
||||||
|
"\acart_id\x18\x01 \x01(\tR\x06cartId\x12*\n" +
|
||||||
|
"\x04type\x18\x02 \x01(\x0e2\x16.messages.MutationTypeR\x04type\x12\x18\n" +
|
||||||
|
"\apayload\x18\x03 \x01(\fR\apayload\x12)\n" +
|
||||||
|
"\x10client_timestamp\x18\x04 \x01(\x03R\x0fclientTimestamp\"J\n" +
|
||||||
|
"\rMutationReply\x12\x1f\n" +
|
||||||
|
"\vstatus_code\x18\x01 \x01(\x05R\n" +
|
||||||
|
"statusCode\x12\x18\n" +
|
||||||
|
"\apayload\x18\x02 \x01(\fR\apayload\"'\n" +
|
||||||
|
"\fStateRequest\x12\x17\n" +
|
||||||
|
"\acart_id\x18\x01 \x01(\tR\x06cartId\"G\n" +
|
||||||
|
"\n" +
|
||||||
|
"StateReply\x12\x1f\n" +
|
||||||
|
"\vstatus_code\x18\x01 \x01(\x05R\n" +
|
||||||
|
"statusCode\x12\x18\n" +
|
||||||
|
"\apayload\x18\x02 \x01(\fR\apayload*\xcd\x02\n" +
|
||||||
|
"\fMutationType\x12\x1d\n" +
|
||||||
|
"\x19MUTATION_TYPE_UNSPECIFIED\x10\x00\x12\x18\n" +
|
||||||
|
"\x14MUTATION_ADD_REQUEST\x10\x01\x12\x15\n" +
|
||||||
|
"\x11MUTATION_ADD_ITEM\x10\x02\x12\x18\n" +
|
||||||
|
"\x14MUTATION_REMOVE_ITEM\x10\x04\x12\x1c\n" +
|
||||||
|
"\x18MUTATION_REMOVE_DELIVERY\x10\x05\x12\x1c\n" +
|
||||||
|
"\x18MUTATION_CHANGE_QUANTITY\x10\x06\x12\x19\n" +
|
||||||
|
"\x15MUTATION_SET_DELIVERY\x10\a\x12\x1d\n" +
|
||||||
|
"\x19MUTATION_SET_PICKUP_POINT\x10\b\x12\"\n" +
|
||||||
|
"\x1eMUTATION_CREATE_CHECKOUT_ORDER\x10\t\x12\x1b\n" +
|
||||||
|
"\x17MUTATION_SET_CART_ITEMS\x10\n" +
|
||||||
|
"\x12\x1c\n" +
|
||||||
|
"\x18MUTATION_ORDER_COMPLETED\x10\v2\x83\x01\n" +
|
||||||
|
"\tCartActor\x12<\n" +
|
||||||
|
"\x06Mutate\x12\x19.messages.MutationRequest\x1a\x17.messages.MutationReply\x128\n" +
|
||||||
|
"\bGetState\x12\x16.messages.StateRequest\x1a\x14.messages.StateReplyB\fZ\n" +
|
||||||
|
".;messagesb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_cart_actor_proto_rawDescOnce sync.Once
|
||||||
|
file_cart_actor_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_cart_actor_proto_rawDescGZIP() []byte {
|
||||||
|
file_cart_actor_proto_rawDescOnce.Do(func() {
|
||||||
|
file_cart_actor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cart_actor_proto_rawDesc), len(file_cart_actor_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_cart_actor_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_cart_actor_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_cart_actor_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||||
|
var file_cart_actor_proto_goTypes = []any{
|
||||||
|
(MutationType)(0), // 0: messages.MutationType
|
||||||
|
(*MutationRequest)(nil), // 1: messages.MutationRequest
|
||||||
|
(*MutationReply)(nil), // 2: messages.MutationReply
|
||||||
|
(*StateRequest)(nil), // 3: messages.StateRequest
|
||||||
|
(*StateReply)(nil), // 4: messages.StateReply
|
||||||
|
}
|
||||||
|
var file_cart_actor_proto_depIdxs = []int32{
|
||||||
|
0, // 0: messages.MutationRequest.type:type_name -> messages.MutationType
|
||||||
|
1, // 1: messages.CartActor.Mutate:input_type -> messages.MutationRequest
|
||||||
|
3, // 2: messages.CartActor.GetState:input_type -> messages.StateRequest
|
||||||
|
2, // 3: messages.CartActor.Mutate:output_type -> messages.MutationReply
|
||||||
|
4, // 4: messages.CartActor.GetState:output_type -> messages.StateReply
|
||||||
|
3, // [3:5] is the sub-list for method output_type
|
||||||
|
1, // [1:3] is the sub-list for method input_type
|
||||||
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
|
1, // [1:1] is the sub-list for extension extendee
|
||||||
|
0, // [0:1] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_cart_actor_proto_init() }
|
||||||
|
func file_cart_actor_proto_init() {
|
||||||
|
if File_cart_actor_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cart_actor_proto_rawDesc), len(file_cart_actor_proto_rawDesc)),
|
||||||
|
NumEnums: 1,
|
||||||
|
NumMessages: 4,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_cart_actor_proto_goTypes,
|
||||||
|
DependencyIndexes: file_cart_actor_proto_depIdxs,
|
||||||
|
EnumInfos: file_cart_actor_proto_enumTypes,
|
||||||
|
MessageInfos: file_cart_actor_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_cart_actor_proto = out.File
|
||||||
|
file_cart_actor_proto_goTypes = nil
|
||||||
|
file_cart_actor_proto_depIdxs = nil
|
||||||
|
}
|
||||||
89
proto/cart_actor.proto
Normal file
89
proto/cart_actor.proto
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package messages;
|
||||||
|
|
||||||
|
option go_package = ".;messages";
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Cart Actor gRPC API (Envelope Variant)
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// This service replaces the legacy custom TCP frame protocol used on port 1337.
|
||||||
|
// It keeps the existing per-mutation proto messages (defined in messages.proto)
|
||||||
|
// serialized into an opaque `bytes payload` field for minimal refactor cost.
|
||||||
|
// The numeric values in MutationType MUST match the legacy message type
|
||||||
|
// constants (see message-types.go) so persisted event logs replay correctly.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// MutationType corresponds 1:1 with the legacy uint16 message type constants.
|
||||||
|
enum MutationType {
|
||||||
|
MUTATION_TYPE_UNSPECIFIED = 0;
|
||||||
|
MUTATION_ADD_REQUEST = 1;
|
||||||
|
MUTATION_ADD_ITEM = 2;
|
||||||
|
// (3 was unused / reserved in legacy framing)
|
||||||
|
MUTATION_REMOVE_ITEM = 4;
|
||||||
|
MUTATION_REMOVE_DELIVERY = 5;
|
||||||
|
MUTATION_CHANGE_QUANTITY = 6;
|
||||||
|
MUTATION_SET_DELIVERY = 7;
|
||||||
|
MUTATION_SET_PICKUP_POINT = 8;
|
||||||
|
MUTATION_CREATE_CHECKOUT_ORDER = 9;
|
||||||
|
MUTATION_SET_CART_ITEMS = 10;
|
||||||
|
MUTATION_ORDER_COMPLETED = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutationRequest is an envelope:
|
||||||
|
// - cart_id: string form of CartId (legacy 16-byte array truncated/padded).
|
||||||
|
// - type: mutation kind (see enum).
|
||||||
|
// - payload: serialized underlying proto message (AddRequest, AddItem, etc.).
|
||||||
|
// - client_timestamp: optional unix timestamp; server sets if zero.
|
||||||
|
message MutationRequest {
|
||||||
|
string cart_id = 1;
|
||||||
|
MutationType type = 2;
|
||||||
|
bytes payload = 3;
|
||||||
|
int64 client_timestamp = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutationReply returns a status code (legacy semantics) plus a JSON payload
|
||||||
|
// representing the full cart state (or an error message if non-200).
|
||||||
|
message MutationReply {
|
||||||
|
int32 status_code = 1;
|
||||||
|
bytes payload = 2; // JSON cart state or error string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateRequest fetches current cart state without mutation.
|
||||||
|
message StateRequest {
|
||||||
|
string cart_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateReply mirrors MutationReply for consistency.
|
||||||
|
message StateReply {
|
||||||
|
int32 status_code = 1;
|
||||||
|
bytes payload = 2; // JSON cart state or error string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CartActor exposes mutation and state retrieval for remote grains.
|
||||||
|
service CartActor {
|
||||||
|
// Mutate applies a single mutation to a cart, creating the cart lazily if needed.
|
||||||
|
rpc Mutate(MutationRequest) returns (MutationReply);
|
||||||
|
|
||||||
|
// GetState retrieves the cart's current state (JSON).
|
||||||
|
rpc GetState(StateRequest) returns (StateReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Notes:
|
||||||
|
//
|
||||||
|
// 1. Generation:
|
||||||
|
// protoc --go_out=. --go_opt=paths=source_relative \
|
||||||
|
// --go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||||
|
// cart_actor.proto
|
||||||
|
//
|
||||||
|
// 2. Underlying mutation payloads originate from messages.proto definitions.
|
||||||
|
// The server side will route based on MutationType and decode payload bytes
|
||||||
|
// using existing handler registry logic.
|
||||||
|
//
|
||||||
|
// 3. Future Enhancements:
|
||||||
|
// - Replace JSON state payload with a strongly typed CartState proto.
|
||||||
|
// - Add streaming RPC (e.g. WatchState) for live updates.
|
||||||
|
// - Migrate control plane (negotiate/ownership) into a separate proto
|
||||||
|
// (control_plane.proto) as per the migration plan.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
167
proto/cart_actor_grpc.pb.go
Normal file
167
proto/cart_actor_grpc.pb.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
|
// - protoc v3.21.12
|
||||||
|
// source: cart_actor.proto
|
||||||
|
|
||||||
|
package messages
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
CartActor_Mutate_FullMethodName = "/messages.CartActor/Mutate"
|
||||||
|
CartActor_GetState_FullMethodName = "/messages.CartActor/GetState"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CartActorClient is the client API for CartActor service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
//
|
||||||
|
// CartActor exposes mutation and state retrieval for remote grains.
|
||||||
|
type CartActorClient interface {
|
||||||
|
// Mutate applies a single mutation to a cart, creating the cart lazily if needed.
|
||||||
|
Mutate(ctx context.Context, in *MutationRequest, opts ...grpc.CallOption) (*MutationReply, error)
|
||||||
|
// GetState retrieves the cart's current state (JSON).
|
||||||
|
GetState(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateReply, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type cartActorClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCartActorClient(cc grpc.ClientConnInterface) CartActorClient {
|
||||||
|
return &cartActorClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cartActorClient) Mutate(ctx context.Context, in *MutationRequest, opts ...grpc.CallOption) (*MutationReply, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(MutationReply)
|
||||||
|
err := c.cc.Invoke(ctx, CartActor_Mutate_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cartActorClient) GetState(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateReply, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(StateReply)
|
||||||
|
err := c.cc.Invoke(ctx, CartActor_GetState_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CartActorServer is the server API for CartActor service.
|
||||||
|
// All implementations must embed UnimplementedCartActorServer
|
||||||
|
// for forward compatibility.
|
||||||
|
//
|
||||||
|
// CartActor exposes mutation and state retrieval for remote grains.
|
||||||
|
type CartActorServer interface {
|
||||||
|
// Mutate applies a single mutation to a cart, creating the cart lazily if needed.
|
||||||
|
Mutate(context.Context, *MutationRequest) (*MutationReply, error)
|
||||||
|
// GetState retrieves the cart's current state (JSON).
|
||||||
|
GetState(context.Context, *StateRequest) (*StateReply, error)
|
||||||
|
mustEmbedUnimplementedCartActorServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedCartActorServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedCartActorServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedCartActorServer) Mutate(context.Context, *MutationRequest) (*MutationReply, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Mutate not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCartActorServer) GetState(context.Context, *StateRequest) (*StateReply, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetState not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCartActorServer) mustEmbedUnimplementedCartActorServer() {}
|
||||||
|
func (UnimplementedCartActorServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeCartActorServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to CartActorServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeCartActorServer interface {
|
||||||
|
mustEmbedUnimplementedCartActorServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterCartActorServer(s grpc.ServiceRegistrar, srv CartActorServer) {
|
||||||
|
// If the following call pancis, it indicates UnimplementedCartActorServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&CartActor_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CartActor_Mutate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(MutationRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CartActorServer).Mutate(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CartActor_Mutate_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CartActorServer).Mutate(ctx, req.(*MutationRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CartActor_GetState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(StateRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CartActorServer).GetState(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CartActor_GetState_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CartActorServer).GetState(ctx, req.(*StateRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CartActor_ServiceDesc is the grpc.ServiceDesc for CartActor service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var CartActor_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "messages.CartActor",
|
||||||
|
HandlerType: (*CartActorServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "Mutate",
|
||||||
|
Handler: _CartActor_Mutate_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetState",
|
||||||
|
Handler: _CartActor_GetState_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "cart_actor.proto",
|
||||||
|
}
|
||||||
496
proto/control_plane.pb.go
Normal file
496
proto/control_plane.pb.go
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.10
|
||||||
|
// protoc v3.21.12
|
||||||
|
// source: control_plane.proto
|
||||||
|
|
||||||
|
package messages
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Empty request placeholder (common pattern).
|
||||||
|
type Empty struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Empty) Reset() {
|
||||||
|
*x = Empty{}
|
||||||
|
mi := &file_control_plane_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Empty) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Empty) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Empty) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_control_plane_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Empty) Descriptor() ([]byte, []int) {
|
||||||
|
return file_control_plane_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping reply includes responding host and its current unix time (seconds).
|
||||||
|
type PingReply struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||||
|
UnixTime int64 `protobuf:"varint,2,opt,name=unix_time,json=unixTime,proto3" json:"unix_time,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PingReply) Reset() {
|
||||||
|
*x = PingReply{}
|
||||||
|
mi := &file_control_plane_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PingReply) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*PingReply) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *PingReply) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_control_plane_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use PingReply.ProtoReflect.Descriptor instead.
|
||||||
|
func (*PingReply) Descriptor() ([]byte, []int) {
|
||||||
|
return file_control_plane_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PingReply) GetHost() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Host
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PingReply) GetUnixTime() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UnixTime
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegotiateRequest carries the caller's full view of known hosts (including self).
|
||||||
|
type NegotiateRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
KnownHosts []string `protobuf:"bytes,1,rep,name=known_hosts,json=knownHosts,proto3" json:"known_hosts,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NegotiateRequest) Reset() {
|
||||||
|
*x = NegotiateRequest{}
|
||||||
|
mi := &file_control_plane_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NegotiateRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NegotiateRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *NegotiateRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_control_plane_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NegotiateRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*NegotiateRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_control_plane_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NegotiateRequest) GetKnownHosts() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.KnownHosts
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegotiateReply returns the callee's healthy hosts (including itself).
|
||||||
|
type NegotiateReply struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Hosts []string `protobuf:"bytes,1,rep,name=hosts,proto3" json:"hosts,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NegotiateReply) Reset() {
|
||||||
|
*x = NegotiateReply{}
|
||||||
|
mi := &file_control_plane_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NegotiateReply) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NegotiateReply) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *NegotiateReply) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_control_plane_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NegotiateReply.ProtoReflect.Descriptor instead.
|
||||||
|
func (*NegotiateReply) Descriptor() ([]byte, []int) {
|
||||||
|
return file_control_plane_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NegotiateReply) GetHosts() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Hosts
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CartIdsReply returns the list of cart IDs (string form) currently owned locally.
|
||||||
|
type CartIdsReply struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
CartIds []string `protobuf:"bytes,1,rep,name=cart_ids,json=cartIds,proto3" json:"cart_ids,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CartIdsReply) Reset() {
|
||||||
|
*x = CartIdsReply{}
|
||||||
|
mi := &file_control_plane_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CartIdsReply) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CartIdsReply) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CartIdsReply) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_control_plane_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CartIdsReply.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CartIdsReply) Descriptor() ([]byte, []int) {
|
||||||
|
return file_control_plane_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CartIdsReply) GetCartIds() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.CartIds
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OwnerChangeRequest notifies peers that ownership of a cart moved (or is moving) to new_host.
|
||||||
|
type OwnerChangeRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
CartId string `protobuf:"bytes,1,opt,name=cart_id,json=cartId,proto3" json:"cart_id,omitempty"`
|
||||||
|
NewHost string `protobuf:"bytes,2,opt,name=new_host,json=newHost,proto3" json:"new_host,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OwnerChangeRequest) Reset() {
|
||||||
|
*x = OwnerChangeRequest{}
|
||||||
|
mi := &file_control_plane_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OwnerChangeRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*OwnerChangeRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *OwnerChangeRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_control_plane_proto_msgTypes[5]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use OwnerChangeRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*OwnerChangeRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_control_plane_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OwnerChangeRequest) GetCartId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.CartId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OwnerChangeRequest) GetNewHost() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.NewHost
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// OwnerChangeAck indicates acceptance or rejection of an ownership change.
|
||||||
|
type OwnerChangeAck struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"`
|
||||||
|
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OwnerChangeAck) Reset() {
|
||||||
|
*x = OwnerChangeAck{}
|
||||||
|
mi := &file_control_plane_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OwnerChangeAck) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*OwnerChangeAck) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *OwnerChangeAck) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_control_plane_proto_msgTypes[6]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use OwnerChangeAck.ProtoReflect.Descriptor instead.
|
||||||
|
func (*OwnerChangeAck) Descriptor() ([]byte, []int) {
|
||||||
|
return file_control_plane_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OwnerChangeAck) GetAccepted() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Accepted
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OwnerChangeAck) GetMessage() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Message
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosingNotice notifies peers this host is terminating (so they can drop / re-resolve).
|
||||||
|
type ClosingNotice struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ClosingNotice) Reset() {
|
||||||
|
*x = ClosingNotice{}
|
||||||
|
mi := &file_control_plane_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ClosingNotice) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ClosingNotice) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ClosingNotice) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_control_plane_proto_msgTypes[7]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ClosingNotice.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ClosingNotice) Descriptor() ([]byte, []int) {
|
||||||
|
return file_control_plane_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ClosingNotice) GetHost() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Host
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_control_plane_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_control_plane_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x13control_plane.proto\x12\bmessages\"\a\n" +
|
||||||
|
"\x05Empty\"<\n" +
|
||||||
|
"\tPingReply\x12\x12\n" +
|
||||||
|
"\x04host\x18\x01 \x01(\tR\x04host\x12\x1b\n" +
|
||||||
|
"\tunix_time\x18\x02 \x01(\x03R\bunixTime\"3\n" +
|
||||||
|
"\x10NegotiateRequest\x12\x1f\n" +
|
||||||
|
"\vknown_hosts\x18\x01 \x03(\tR\n" +
|
||||||
|
"knownHosts\"&\n" +
|
||||||
|
"\x0eNegotiateReply\x12\x14\n" +
|
||||||
|
"\x05hosts\x18\x01 \x03(\tR\x05hosts\")\n" +
|
||||||
|
"\fCartIdsReply\x12\x19\n" +
|
||||||
|
"\bcart_ids\x18\x01 \x03(\tR\acartIds\"H\n" +
|
||||||
|
"\x12OwnerChangeRequest\x12\x17\n" +
|
||||||
|
"\acart_id\x18\x01 \x01(\tR\x06cartId\x12\x19\n" +
|
||||||
|
"\bnew_host\x18\x02 \x01(\tR\anewHost\"F\n" +
|
||||||
|
"\x0eOwnerChangeAck\x12\x1a\n" +
|
||||||
|
"\baccepted\x18\x01 \x01(\bR\baccepted\x12\x18\n" +
|
||||||
|
"\amessage\x18\x02 \x01(\tR\amessage\"#\n" +
|
||||||
|
"\rClosingNotice\x12\x12\n" +
|
||||||
|
"\x04host\x18\x01 \x01(\tR\x04host2\xbc\x02\n" +
|
||||||
|
"\fControlPlane\x12,\n" +
|
||||||
|
"\x04Ping\x12\x0f.messages.Empty\x1a\x13.messages.PingReply\x12A\n" +
|
||||||
|
"\tNegotiate\x12\x1a.messages.NegotiateRequest\x1a\x18.messages.NegotiateReply\x125\n" +
|
||||||
|
"\n" +
|
||||||
|
"GetCartIds\x12\x0f.messages.Empty\x1a\x16.messages.CartIdsReply\x12F\n" +
|
||||||
|
"\fConfirmOwner\x12\x1c.messages.OwnerChangeRequest\x1a\x18.messages.OwnerChangeAck\x12<\n" +
|
||||||
|
"\aClosing\x12\x17.messages.ClosingNotice\x1a\x18.messages.OwnerChangeAckB\fZ\n" +
|
||||||
|
".;messagesb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_control_plane_proto_rawDescOnce sync.Once
|
||||||
|
file_control_plane_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_control_plane_proto_rawDescGZIP() []byte {
|
||||||
|
file_control_plane_proto_rawDescOnce.Do(func() {
|
||||||
|
file_control_plane_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_control_plane_proto_rawDesc), len(file_control_plane_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_control_plane_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_control_plane_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||||
|
var file_control_plane_proto_goTypes = []any{
|
||||||
|
(*Empty)(nil), // 0: messages.Empty
|
||||||
|
(*PingReply)(nil), // 1: messages.PingReply
|
||||||
|
(*NegotiateRequest)(nil), // 2: messages.NegotiateRequest
|
||||||
|
(*NegotiateReply)(nil), // 3: messages.NegotiateReply
|
||||||
|
(*CartIdsReply)(nil), // 4: messages.CartIdsReply
|
||||||
|
(*OwnerChangeRequest)(nil), // 5: messages.OwnerChangeRequest
|
||||||
|
(*OwnerChangeAck)(nil), // 6: messages.OwnerChangeAck
|
||||||
|
(*ClosingNotice)(nil), // 7: messages.ClosingNotice
|
||||||
|
}
|
||||||
|
var file_control_plane_proto_depIdxs = []int32{
|
||||||
|
0, // 0: messages.ControlPlane.Ping:input_type -> messages.Empty
|
||||||
|
2, // 1: messages.ControlPlane.Negotiate:input_type -> messages.NegotiateRequest
|
||||||
|
0, // 2: messages.ControlPlane.GetCartIds:input_type -> messages.Empty
|
||||||
|
5, // 3: messages.ControlPlane.ConfirmOwner:input_type -> messages.OwnerChangeRequest
|
||||||
|
7, // 4: messages.ControlPlane.Closing:input_type -> messages.ClosingNotice
|
||||||
|
1, // 5: messages.ControlPlane.Ping:output_type -> messages.PingReply
|
||||||
|
3, // 6: messages.ControlPlane.Negotiate:output_type -> messages.NegotiateReply
|
||||||
|
4, // 7: messages.ControlPlane.GetCartIds:output_type -> messages.CartIdsReply
|
||||||
|
6, // 8: messages.ControlPlane.ConfirmOwner:output_type -> messages.OwnerChangeAck
|
||||||
|
6, // 9: messages.ControlPlane.Closing:output_type -> messages.OwnerChangeAck
|
||||||
|
5, // [5:10] is the sub-list for method output_type
|
||||||
|
0, // [0:5] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_control_plane_proto_init() }
|
||||||
|
func file_control_plane_proto_init() {
|
||||||
|
if File_control_plane_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_control_plane_proto_rawDesc), len(file_control_plane_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 8,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_control_plane_proto_goTypes,
|
||||||
|
DependencyIndexes: file_control_plane_proto_depIdxs,
|
||||||
|
MessageInfos: file_control_plane_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_control_plane_proto = out.File
|
||||||
|
file_control_plane_proto_goTypes = nil
|
||||||
|
file_control_plane_proto_depIdxs = nil
|
||||||
|
}
|
||||||
89
proto/control_plane.proto
Normal file
89
proto/control_plane.proto
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package messages;
|
||||||
|
|
||||||
|
option go_package = ".;messages";
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Control Plane gRPC API
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Replaces the legacy custom frame-based control channel (previously port 1338).
|
||||||
|
// Responsibilities:
|
||||||
|
// - Liveness (Ping)
|
||||||
|
// - Membership negotiation (Negotiate)
|
||||||
|
// - Cart ownership change propagation (ConfirmOwner)
|
||||||
|
// - Cart ID listing for remote grain spawning (GetCartIds)
|
||||||
|
// - Graceful shutdown notifications (Closing)
|
||||||
|
// No authentication / TLS is defined initially (can be added later).
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Empty request placeholder (common pattern).
|
||||||
|
message Empty {}
|
||||||
|
|
||||||
|
// Ping reply includes responding host and its current unix time (seconds).
|
||||||
|
message PingReply {
|
||||||
|
string host = 1;
|
||||||
|
int64 unix_time = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegotiateRequest carries the caller's full view of known hosts (including self).
|
||||||
|
message NegotiateRequest {
|
||||||
|
repeated string known_hosts = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegotiateReply returns the callee's healthy hosts (including itself).
|
||||||
|
message NegotiateReply {
|
||||||
|
repeated string hosts = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CartIdsReply returns the list of cart IDs (string form) currently owned locally.
|
||||||
|
message CartIdsReply {
|
||||||
|
repeated string cart_ids = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OwnerChangeRequest notifies peers that ownership of a cart moved (or is moving) to new_host.
|
||||||
|
message OwnerChangeRequest {
|
||||||
|
string cart_id = 1;
|
||||||
|
string new_host = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OwnerChangeAck indicates acceptance or rejection of an ownership change.
|
||||||
|
message OwnerChangeAck {
|
||||||
|
bool accepted = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosingNotice notifies peers this host is terminating (so they can drop / re-resolve).
|
||||||
|
message ClosingNotice {
|
||||||
|
string host = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControlPlane defines cluster coordination and ownership operations.
|
||||||
|
service ControlPlane {
|
||||||
|
// Ping for liveness; lightweight health signal.
|
||||||
|
rpc Ping(Empty) returns (PingReply);
|
||||||
|
|
||||||
|
// Negotiate merges host views; used during discovery & convergence.
|
||||||
|
rpc Negotiate(NegotiateRequest) returns (NegotiateReply);
|
||||||
|
|
||||||
|
// GetCartIds lists currently owned cart IDs on this node.
|
||||||
|
rpc GetCartIds(Empty) returns (CartIdsReply);
|
||||||
|
|
||||||
|
// ConfirmOwner announces/asks peers to acknowledge ownership transfer.
|
||||||
|
rpc ConfirmOwner(OwnerChangeRequest) returns (OwnerChangeAck);
|
||||||
|
|
||||||
|
// Closing announces graceful shutdown so peers can proactively adjust.
|
||||||
|
rpc Closing(ClosingNotice) returns (OwnerChangeAck);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Generation Instructions:
|
||||||
|
// protoc --go_out=. --go_opt=paths=source_relative \
|
||||||
|
// --go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||||
|
// control_plane.proto
|
||||||
|
//
|
||||||
|
// Future Enhancements:
|
||||||
|
// - Add a streaming membership watch (server -> client) for immediate updates.
|
||||||
|
// - Add TLS / mTLS for secure intra-cluster communication.
|
||||||
|
// - Add richer health metadata (load, grain count) in PingReply.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
287
proto/control_plane_grpc.pb.go
Normal file
287
proto/control_plane_grpc.pb.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
|
// - protoc v3.21.12
|
||||||
|
// source: control_plane.proto
|
||||||
|
|
||||||
|
package messages
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
ControlPlane_Ping_FullMethodName = "/messages.ControlPlane/Ping"
|
||||||
|
ControlPlane_Negotiate_FullMethodName = "/messages.ControlPlane/Negotiate"
|
||||||
|
ControlPlane_GetCartIds_FullMethodName = "/messages.ControlPlane/GetCartIds"
|
||||||
|
ControlPlane_ConfirmOwner_FullMethodName = "/messages.ControlPlane/ConfirmOwner"
|
||||||
|
ControlPlane_Closing_FullMethodName = "/messages.ControlPlane/Closing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ControlPlaneClient is the client API for ControlPlane service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
//
|
||||||
|
// ControlPlane defines cluster coordination and ownership operations.
|
||||||
|
type ControlPlaneClient interface {
|
||||||
|
// Ping for liveness; lightweight health signal.
|
||||||
|
Ping(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PingReply, error)
|
||||||
|
// Negotiate merges host views; used during discovery & convergence.
|
||||||
|
Negotiate(ctx context.Context, in *NegotiateRequest, opts ...grpc.CallOption) (*NegotiateReply, error)
|
||||||
|
// GetCartIds lists currently owned cart IDs on this node.
|
||||||
|
GetCartIds(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*CartIdsReply, error)
|
||||||
|
// ConfirmOwner announces/asks peers to acknowledge ownership transfer.
|
||||||
|
ConfirmOwner(ctx context.Context, in *OwnerChangeRequest, opts ...grpc.CallOption) (*OwnerChangeAck, error)
|
||||||
|
// Closing announces graceful shutdown so peers can proactively adjust.
|
||||||
|
Closing(ctx context.Context, in *ClosingNotice, opts ...grpc.CallOption) (*OwnerChangeAck, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type controlPlaneClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewControlPlaneClient(cc grpc.ClientConnInterface) ControlPlaneClient {
|
||||||
|
return &controlPlaneClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controlPlaneClient) Ping(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PingReply, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(PingReply)
|
||||||
|
err := c.cc.Invoke(ctx, ControlPlane_Ping_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controlPlaneClient) Negotiate(ctx context.Context, in *NegotiateRequest, opts ...grpc.CallOption) (*NegotiateReply, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(NegotiateReply)
|
||||||
|
err := c.cc.Invoke(ctx, ControlPlane_Negotiate_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controlPlaneClient) GetCartIds(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*CartIdsReply, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(CartIdsReply)
|
||||||
|
err := c.cc.Invoke(ctx, ControlPlane_GetCartIds_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controlPlaneClient) ConfirmOwner(ctx context.Context, in *OwnerChangeRequest, opts ...grpc.CallOption) (*OwnerChangeAck, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(OwnerChangeAck)
|
||||||
|
err := c.cc.Invoke(ctx, ControlPlane_ConfirmOwner_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controlPlaneClient) Closing(ctx context.Context, in *ClosingNotice, opts ...grpc.CallOption) (*OwnerChangeAck, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(OwnerChangeAck)
|
||||||
|
err := c.cc.Invoke(ctx, ControlPlane_Closing_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControlPlaneServer is the server API for ControlPlane service.
|
||||||
|
// All implementations must embed UnimplementedControlPlaneServer
|
||||||
|
// for forward compatibility.
|
||||||
|
//
|
||||||
|
// ControlPlane defines cluster coordination and ownership operations.
|
||||||
|
type ControlPlaneServer interface {
|
||||||
|
// Ping for liveness; lightweight health signal.
|
||||||
|
Ping(context.Context, *Empty) (*PingReply, error)
|
||||||
|
// Negotiate merges host views; used during discovery & convergence.
|
||||||
|
Negotiate(context.Context, *NegotiateRequest) (*NegotiateReply, error)
|
||||||
|
// GetCartIds lists currently owned cart IDs on this node.
|
||||||
|
GetCartIds(context.Context, *Empty) (*CartIdsReply, error)
|
||||||
|
// ConfirmOwner announces/asks peers to acknowledge ownership transfer.
|
||||||
|
ConfirmOwner(context.Context, *OwnerChangeRequest) (*OwnerChangeAck, error)
|
||||||
|
// Closing announces graceful shutdown so peers can proactively adjust.
|
||||||
|
Closing(context.Context, *ClosingNotice) (*OwnerChangeAck, error)
|
||||||
|
mustEmbedUnimplementedControlPlaneServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedControlPlaneServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedControlPlaneServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedControlPlaneServer) Ping(context.Context, *Empty) (*PingReply, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedControlPlaneServer) Negotiate(context.Context, *NegotiateRequest) (*NegotiateReply, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Negotiate not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedControlPlaneServer) GetCartIds(context.Context, *Empty) (*CartIdsReply, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetCartIds not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedControlPlaneServer) ConfirmOwner(context.Context, *OwnerChangeRequest) (*OwnerChangeAck, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ConfirmOwner not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedControlPlaneServer) Closing(context.Context, *ClosingNotice) (*OwnerChangeAck, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Closing not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedControlPlaneServer) mustEmbedUnimplementedControlPlaneServer() {}
|
||||||
|
func (UnimplementedControlPlaneServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeControlPlaneServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to ControlPlaneServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeControlPlaneServer interface {
|
||||||
|
mustEmbedUnimplementedControlPlaneServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterControlPlaneServer(s grpc.ServiceRegistrar, srv ControlPlaneServer) {
|
||||||
|
// If the following call pancis, it indicates UnimplementedControlPlaneServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&ControlPlane_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ControlPlane_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ControlPlaneServer).Ping(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ControlPlane_Ping_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ControlPlaneServer).Ping(ctx, req.(*Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ControlPlane_Negotiate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(NegotiateRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ControlPlaneServer).Negotiate(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ControlPlane_Negotiate_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ControlPlaneServer).Negotiate(ctx, req.(*NegotiateRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ControlPlane_GetCartIds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ControlPlaneServer).GetCartIds(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ControlPlane_GetCartIds_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ControlPlaneServer).GetCartIds(ctx, req.(*Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ControlPlane_ConfirmOwner_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(OwnerChangeRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ControlPlaneServer).ConfirmOwner(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ControlPlane_ConfirmOwner_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ControlPlaneServer).ConfirmOwner(ctx, req.(*OwnerChangeRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ControlPlane_Closing_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ClosingNotice)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ControlPlaneServer).Closing(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ControlPlane_Closing_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ControlPlaneServer).Closing(ctx, req.(*ClosingNotice))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControlPlane_ServiceDesc is the grpc.ServiceDesc for ControlPlane service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var ControlPlane_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "messages.ControlPlane",
|
||||||
|
HandlerType: (*ControlPlaneServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "Ping",
|
||||||
|
Handler: _ControlPlane_Ping_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Negotiate",
|
||||||
|
Handler: _ControlPlane_Negotiate_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetCartIds",
|
||||||
|
Handler: _ControlPlane_GetCartIds_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ConfirmOwner",
|
||||||
|
Handler: _ControlPlane_ConfirmOwner_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Closing",
|
||||||
|
Handler: _ControlPlane_Closing_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "control_plane.proto",
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.35.1
|
// protoc-gen-go v1.36.10
|
||||||
// protoc v5.28.3
|
// protoc v3.21.12
|
||||||
// source: messages.proto
|
// source: messages.proto
|
||||||
|
|
||||||
package messages
|
package messages
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,12 +22,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AddRequest struct {
|
type AddRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Quantity int32 `protobuf:"varint,1,opt,name=quantity,proto3" json:"quantity,omitempty"`
|
||||||
|
Sku string `protobuf:"bytes,2,opt,name=sku,proto3" json:"sku,omitempty"`
|
||||||
|
Country string `protobuf:"bytes,3,opt,name=country,proto3" json:"country,omitempty"`
|
||||||
|
StoreId *string `protobuf:"bytes,4,opt,name=storeId,proto3,oneof" json:"storeId,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Quantity int32 `protobuf:"varint,1,opt,name=Quantity,proto3" json:"quantity,omitempty"`
|
|
||||||
Sku string `protobuf:"bytes,2,opt,name=Sku,proto3" json:"sku,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddRequest) Reset() {
|
func (x *AddRequest) Reset() {
|
||||||
@@ -73,27 +75,95 @@ func (x *AddRequest) GetSku() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddItem struct {
|
func (x *AddRequest) GetCountry() string {
|
||||||
state protoimpl.MessageState
|
if x != nil {
|
||||||
sizeCache protoimpl.SizeCache
|
return x.Country
|
||||||
unknownFields protoimpl.UnknownFields
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
Quantity int32 `protobuf:"varint,2,opt,name=Quantity,proto3" json:"quantity,omitempty"`
|
func (x *AddRequest) GetStoreId() string {
|
||||||
Price int64 `protobuf:"varint,3,opt,name=Price,proto3" json:"price,omitempty"`
|
if x != nil && x.StoreId != nil {
|
||||||
OrgPrice int64 `protobuf:"varint,9,opt,name=OrgPrice,proto3" json:"orgPrice,omitempty"`
|
return *x.StoreId
|
||||||
Sku string `protobuf:"bytes,4,opt,name=Sku,proto3" json:"sku,omitempty"`
|
}
|
||||||
Name string `protobuf:"bytes,5,opt,name=Name,proto3" json:"name,omitempty"`
|
return ""
|
||||||
Image string `protobuf:"bytes,6,opt,name=Image,proto3" json:"image,omitempty"`
|
}
|
||||||
Stock int32 `protobuf:"varint,7,opt,name=Stock,proto3" json:"stock,omitempty"`
|
|
||||||
Tax int32 `protobuf:"varint,8,opt,name=Tax,proto3" json:"tax,omitempty"`
|
type SetCartRequest struct {
|
||||||
Disclaimer string `protobuf:"bytes,10,opt,name=Disclaimer,proto3" json:"disclaimer,omitempty"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
ArticleType string `protobuf:"bytes,11,opt,name=ArticleType,proto3" json:"articleType,omitempty"`
|
Items []*AddRequest `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
|
||||||
Outlet *string `protobuf:"bytes,12,opt,name=Outlet,proto3,oneof" json:"outlet,omitempty"`
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SetCartRequest) Reset() {
|
||||||
|
*x = SetCartRequest{}
|
||||||
|
mi := &file_messages_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SetCartRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SetCartRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SetCartRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_messages_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SetCartRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SetCartRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_messages_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SetCartRequest) GetItems() []*AddRequest {
|
||||||
|
if x != nil {
|
||||||
|
return x.Items
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddItem struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ItemId int64 `protobuf:"varint,1,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"`
|
||||||
|
Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"`
|
||||||
|
Price int64 `protobuf:"varint,3,opt,name=price,proto3" json:"price,omitempty"`
|
||||||
|
OrgPrice int64 `protobuf:"varint,9,opt,name=orgPrice,proto3" json:"orgPrice,omitempty"`
|
||||||
|
Sku string `protobuf:"bytes,4,opt,name=sku,proto3" json:"sku,omitempty"`
|
||||||
|
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Image string `protobuf:"bytes,6,opt,name=image,proto3" json:"image,omitempty"`
|
||||||
|
Stock int32 `protobuf:"varint,7,opt,name=stock,proto3" json:"stock,omitempty"`
|
||||||
|
Tax int32 `protobuf:"varint,8,opt,name=tax,proto3" json:"tax,omitempty"`
|
||||||
|
Brand string `protobuf:"bytes,13,opt,name=brand,proto3" json:"brand,omitempty"`
|
||||||
|
Category string `protobuf:"bytes,14,opt,name=category,proto3" json:"category,omitempty"`
|
||||||
|
Category2 string `protobuf:"bytes,15,opt,name=category2,proto3" json:"category2,omitempty"`
|
||||||
|
Category3 string `protobuf:"bytes,16,opt,name=category3,proto3" json:"category3,omitempty"`
|
||||||
|
Category4 string `protobuf:"bytes,17,opt,name=category4,proto3" json:"category4,omitempty"`
|
||||||
|
Category5 string `protobuf:"bytes,18,opt,name=category5,proto3" json:"category5,omitempty"`
|
||||||
|
Disclaimer string `protobuf:"bytes,10,opt,name=disclaimer,proto3" json:"disclaimer,omitempty"`
|
||||||
|
ArticleType string `protobuf:"bytes,11,opt,name=articleType,proto3" json:"articleType,omitempty"`
|
||||||
|
SellerId string `protobuf:"bytes,19,opt,name=sellerId,proto3" json:"sellerId,omitempty"`
|
||||||
|
SellerName string `protobuf:"bytes,20,opt,name=sellerName,proto3" json:"sellerName,omitempty"`
|
||||||
|
Country string `protobuf:"bytes,21,opt,name=country,proto3" json:"country,omitempty"`
|
||||||
|
Outlet *string `protobuf:"bytes,12,opt,name=outlet,proto3,oneof" json:"outlet,omitempty"`
|
||||||
|
StoreId *string `protobuf:"bytes,22,opt,name=storeId,proto3,oneof" json:"storeId,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddItem) Reset() {
|
func (x *AddItem) Reset() {
|
||||||
*x = AddItem{}
|
*x = AddItem{}
|
||||||
mi := &file_messages_proto_msgTypes[1]
|
mi := &file_messages_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -105,7 +175,7 @@ func (x *AddItem) String() string {
|
|||||||
func (*AddItem) ProtoMessage() {}
|
func (*AddItem) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *AddItem) ProtoReflect() protoreflect.Message {
|
func (x *AddItem) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_messages_proto_msgTypes[1]
|
mi := &file_messages_proto_msgTypes[2]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -118,7 +188,14 @@ func (x *AddItem) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use AddItem.ProtoReflect.Descriptor instead.
|
// Deprecated: Use AddItem.ProtoReflect.Descriptor instead.
|
||||||
func (*AddItem) Descriptor() ([]byte, []int) {
|
func (*AddItem) Descriptor() ([]byte, []int) {
|
||||||
return file_messages_proto_rawDescGZIP(), []int{1}
|
return file_messages_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetItemId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ItemId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddItem) GetQuantity() int32 {
|
func (x *AddItem) GetQuantity() int32 {
|
||||||
@@ -177,6 +254,48 @@ func (x *AddItem) GetTax() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetBrand() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Brand
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetCategory() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Category
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetCategory2() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Category2
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetCategory3() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Category3
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetCategory4() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Category4
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetCategory5() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Category5
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *AddItem) GetDisclaimer() string {
|
func (x *AddItem) GetDisclaimer() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Disclaimer
|
return x.Disclaimer
|
||||||
@@ -191,6 +310,27 @@ func (x *AddItem) GetArticleType() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetSellerId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SellerId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetSellerName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SellerName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddItem) GetCountry() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Country
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *AddItem) GetOutlet() string {
|
func (x *AddItem) GetOutlet() string {
|
||||||
if x != nil && x.Outlet != nil {
|
if x != nil && x.Outlet != nil {
|
||||||
return *x.Outlet
|
return *x.Outlet
|
||||||
@@ -198,17 +338,23 @@ func (x *AddItem) GetOutlet() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoveItem struct {
|
func (x *AddItem) GetStoreId() string {
|
||||||
state protoimpl.MessageState
|
if x != nil && x.StoreId != nil {
|
||||||
sizeCache protoimpl.SizeCache
|
return *x.StoreId
|
||||||
unknownFields protoimpl.UnknownFields
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
Id int64 `protobuf:"varint,1,opt,name=Id,proto3" json:"id,omitempty"`
|
type RemoveItem struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id int64 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RemoveItem) Reset() {
|
func (x *RemoveItem) Reset() {
|
||||||
*x = RemoveItem{}
|
*x = RemoveItem{}
|
||||||
mi := &file_messages_proto_msgTypes[2]
|
mi := &file_messages_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -220,7 +366,7 @@ func (x *RemoveItem) String() string {
|
|||||||
func (*RemoveItem) ProtoMessage() {}
|
func (*RemoveItem) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RemoveItem) ProtoReflect() protoreflect.Message {
|
func (x *RemoveItem) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_messages_proto_msgTypes[2]
|
mi := &file_messages_proto_msgTypes[3]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -233,7 +379,7 @@ func (x *RemoveItem) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RemoveItem.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RemoveItem.ProtoReflect.Descriptor instead.
|
||||||
func (*RemoveItem) Descriptor() ([]byte, []int) {
|
func (*RemoveItem) Descriptor() ([]byte, []int) {
|
||||||
return file_messages_proto_rawDescGZIP(), []int{2}
|
return file_messages_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RemoveItem) GetId() int64 {
|
func (x *RemoveItem) GetId() int64 {
|
||||||
@@ -244,17 +390,16 @@ func (x *RemoveItem) GetId() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ChangeQuantity struct {
|
type ChangeQuantity struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Id int64 `protobuf:"varint,1,opt,name=Id,proto3" json:"id,omitempty"`
|
|
||||||
Quantity int32 `protobuf:"varint,2,opt,name=Quantity,proto3" json:"quantity,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ChangeQuantity) Reset() {
|
func (x *ChangeQuantity) Reset() {
|
||||||
*x = ChangeQuantity{}
|
*x = ChangeQuantity{}
|
||||||
mi := &file_messages_proto_msgTypes[3]
|
mi := &file_messages_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -266,7 +411,7 @@ func (x *ChangeQuantity) String() string {
|
|||||||
func (*ChangeQuantity) ProtoMessage() {}
|
func (*ChangeQuantity) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ChangeQuantity) ProtoReflect() protoreflect.Message {
|
func (x *ChangeQuantity) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_messages_proto_msgTypes[3]
|
mi := &file_messages_proto_msgTypes[4]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -279,7 +424,7 @@ func (x *ChangeQuantity) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ChangeQuantity.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ChangeQuantity.ProtoReflect.Descriptor instead.
|
||||||
func (*ChangeQuantity) Descriptor() ([]byte, []int) {
|
func (*ChangeQuantity) Descriptor() ([]byte, []int) {
|
||||||
return file_messages_proto_rawDescGZIP(), []int{3}
|
return file_messages_proto_rawDescGZIP(), []int{4}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ChangeQuantity) GetId() int64 {
|
func (x *ChangeQuantity) GetId() int64 {
|
||||||
@@ -297,22 +442,21 @@ func (x *ChangeQuantity) GetQuantity() int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SetDelivery struct {
|
type SetDelivery struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"`
|
||||||
|
Items []int64 `protobuf:"varint,2,rep,packed,name=items,proto3" json:"items,omitempty"`
|
||||||
|
PickupPoint *PickupPoint `protobuf:"bytes,3,opt,name=pickupPoint,proto3,oneof" json:"pickupPoint,omitempty"`
|
||||||
|
Country string `protobuf:"bytes,4,opt,name=country,proto3" json:"country,omitempty"`
|
||||||
|
Zip string `protobuf:"bytes,5,opt,name=zip,proto3" json:"zip,omitempty"`
|
||||||
|
Address *string `protobuf:"bytes,6,opt,name=address,proto3,oneof" json:"address,omitempty"`
|
||||||
|
City *string `protobuf:"bytes,7,opt,name=city,proto3,oneof" json:"city,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Provider string `protobuf:"bytes,1,opt,name=Provider,proto3" json:"provider,omitempty"`
|
|
||||||
Items []int64 `protobuf:"varint,2,rep,packed,name=Items,proto3" json:"items,omitempty"`
|
|
||||||
PickupPoint *PickupPoint `protobuf:"bytes,3,opt,name=PickupPoint,proto3,oneof" json:"pickupPoint,omitempty"`
|
|
||||||
Country string `protobuf:"bytes,4,opt,name=Country,proto3" json:"country,omitempty"`
|
|
||||||
Zip string `protobuf:"bytes,5,opt,name=Zip,proto3" json:"zip,omitempty"`
|
|
||||||
Address *string `protobuf:"bytes,6,opt,name=Address,proto3,oneof" json:"address,omitempty"`
|
|
||||||
City *string `protobuf:"bytes,7,opt,name=City,proto3,oneof" json:"city,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SetDelivery) Reset() {
|
func (x *SetDelivery) Reset() {
|
||||||
*x = SetDelivery{}
|
*x = SetDelivery{}
|
||||||
mi := &file_messages_proto_msgTypes[4]
|
mi := &file_messages_proto_msgTypes[5]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -324,7 +468,7 @@ func (x *SetDelivery) String() string {
|
|||||||
func (*SetDelivery) ProtoMessage() {}
|
func (*SetDelivery) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *SetDelivery) ProtoReflect() protoreflect.Message {
|
func (x *SetDelivery) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_messages_proto_msgTypes[4]
|
mi := &file_messages_proto_msgTypes[5]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -337,7 +481,7 @@ func (x *SetDelivery) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use SetDelivery.ProtoReflect.Descriptor instead.
|
// Deprecated: Use SetDelivery.ProtoReflect.Descriptor instead.
|
||||||
func (*SetDelivery) Descriptor() ([]byte, []int) {
|
func (*SetDelivery) Descriptor() ([]byte, []int) {
|
||||||
return file_messages_proto_rawDescGZIP(), []int{4}
|
return file_messages_proto_rawDescGZIP(), []int{5}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SetDelivery) GetProvider() string {
|
func (x *SetDelivery) GetProvider() string {
|
||||||
@@ -390,22 +534,21 @@ func (x *SetDelivery) GetCity() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SetPickupPoint struct {
|
type SetPickupPoint struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
DeliveryId int64 `protobuf:"varint,1,opt,name=deliveryId,proto3" json:"deliveryId,omitempty"`
|
||||||
|
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Name *string `protobuf:"bytes,3,opt,name=name,proto3,oneof" json:"name,omitempty"`
|
||||||
|
Address *string `protobuf:"bytes,4,opt,name=address,proto3,oneof" json:"address,omitempty"`
|
||||||
|
City *string `protobuf:"bytes,5,opt,name=city,proto3,oneof" json:"city,omitempty"`
|
||||||
|
Zip *string `protobuf:"bytes,6,opt,name=zip,proto3,oneof" json:"zip,omitempty"`
|
||||||
|
Country *string `protobuf:"bytes,7,opt,name=country,proto3,oneof" json:"country,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
DeliveryId int64 `protobuf:"varint,1,opt,name=DeliveryId,proto3" json:"deliveryId,omitempty"`
|
|
||||||
Id string `protobuf:"bytes,2,opt,name=Id,proto3" json:"id,omitempty"`
|
|
||||||
Name *string `protobuf:"bytes,3,opt,name=Name,proto3,oneof" json:"name,omitempty"`
|
|
||||||
Address *string `protobuf:"bytes,4,opt,name=Address,proto3,oneof" json:"address,omitempty"`
|
|
||||||
City *string `protobuf:"bytes,5,opt,name=City,proto3,oneof" json:"city,omitempty"`
|
|
||||||
Zip *string `protobuf:"bytes,6,opt,name=Zip,proto3,oneof" json:"zip,omitempty"`
|
|
||||||
Country *string `protobuf:"bytes,7,opt,name=Country,proto3,oneof" json:"country,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SetPickupPoint) Reset() {
|
func (x *SetPickupPoint) Reset() {
|
||||||
*x = SetPickupPoint{}
|
*x = SetPickupPoint{}
|
||||||
mi := &file_messages_proto_msgTypes[5]
|
mi := &file_messages_proto_msgTypes[6]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -417,7 +560,7 @@ func (x *SetPickupPoint) String() string {
|
|||||||
func (*SetPickupPoint) ProtoMessage() {}
|
func (*SetPickupPoint) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *SetPickupPoint) ProtoReflect() protoreflect.Message {
|
func (x *SetPickupPoint) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_messages_proto_msgTypes[5]
|
mi := &file_messages_proto_msgTypes[6]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -430,7 +573,7 @@ func (x *SetPickupPoint) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use SetPickupPoint.ProtoReflect.Descriptor instead.
|
// Deprecated: Use SetPickupPoint.ProtoReflect.Descriptor instead.
|
||||||
func (*SetPickupPoint) Descriptor() ([]byte, []int) {
|
func (*SetPickupPoint) Descriptor() ([]byte, []int) {
|
||||||
return file_messages_proto_rawDescGZIP(), []int{5}
|
return file_messages_proto_rawDescGZIP(), []int{6}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SetPickupPoint) GetDeliveryId() int64 {
|
func (x *SetPickupPoint) GetDeliveryId() int64 {
|
||||||
@@ -483,21 +626,20 @@ func (x *SetPickupPoint) GetCountry() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PickupPoint struct {
|
type PickupPoint struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Name *string `protobuf:"bytes,2,opt,name=name,proto3,oneof" json:"name,omitempty"`
|
||||||
|
Address *string `protobuf:"bytes,3,opt,name=address,proto3,oneof" json:"address,omitempty"`
|
||||||
|
City *string `protobuf:"bytes,4,opt,name=city,proto3,oneof" json:"city,omitempty"`
|
||||||
|
Zip *string `protobuf:"bytes,5,opt,name=zip,proto3,oneof" json:"zip,omitempty"`
|
||||||
|
Country *string `protobuf:"bytes,6,opt,name=country,proto3,oneof" json:"country,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Id string `protobuf:"bytes,1,opt,name=Id,proto3" json:"id,omitempty"`
|
|
||||||
Name *string `protobuf:"bytes,2,opt,name=Name,proto3,oneof" json:"name,omitempty"`
|
|
||||||
Address *string `protobuf:"bytes,3,opt,name=Address,proto3,oneof" json:"address,omitempty"`
|
|
||||||
City *string `protobuf:"bytes,4,opt,name=City,proto3,oneof" json:"city,omitempty"`
|
|
||||||
Zip *string `protobuf:"bytes,5,opt,name=Zip,proto3,oneof" json:"zip,omitempty"`
|
|
||||||
Country *string `protobuf:"bytes,6,opt,name=Country,proto3,oneof" json:"country,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PickupPoint) Reset() {
|
func (x *PickupPoint) Reset() {
|
||||||
*x = PickupPoint{}
|
*x = PickupPoint{}
|
||||||
mi := &file_messages_proto_msgTypes[6]
|
mi := &file_messages_proto_msgTypes[7]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -509,7 +651,7 @@ func (x *PickupPoint) String() string {
|
|||||||
func (*PickupPoint) ProtoMessage() {}
|
func (*PickupPoint) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *PickupPoint) ProtoReflect() protoreflect.Message {
|
func (x *PickupPoint) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_messages_proto_msgTypes[6]
|
mi := &file_messages_proto_msgTypes[7]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -522,7 +664,7 @@ func (x *PickupPoint) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use PickupPoint.ProtoReflect.Descriptor instead.
|
// Deprecated: Use PickupPoint.ProtoReflect.Descriptor instead.
|
||||||
func (*PickupPoint) Descriptor() ([]byte, []int) {
|
func (*PickupPoint) Descriptor() ([]byte, []int) {
|
||||||
return file_messages_proto_rawDescGZIP(), []int{6}
|
return file_messages_proto_rawDescGZIP(), []int{7}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PickupPoint) GetId() string {
|
func (x *PickupPoint) GetId() string {
|
||||||
@@ -568,16 +710,15 @@ func (x *PickupPoint) GetCountry() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RemoveDelivery struct {
|
type RemoveDelivery struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Id int64 `protobuf:"varint,1,opt,name=Id,proto3" json:"id,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RemoveDelivery) Reset() {
|
func (x *RemoveDelivery) Reset() {
|
||||||
*x = RemoveDelivery{}
|
*x = RemoveDelivery{}
|
||||||
mi := &file_messages_proto_msgTypes[7]
|
mi := &file_messages_proto_msgTypes[8]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -589,7 +730,7 @@ func (x *RemoveDelivery) String() string {
|
|||||||
func (*RemoveDelivery) ProtoMessage() {}
|
func (*RemoveDelivery) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RemoveDelivery) ProtoReflect() protoreflect.Message {
|
func (x *RemoveDelivery) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_messages_proto_msgTypes[7]
|
mi := &file_messages_proto_msgTypes[8]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -602,7 +743,7 @@ func (x *RemoveDelivery) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RemoveDelivery.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RemoveDelivery.ProtoReflect.Descriptor instead.
|
||||||
func (*RemoveDelivery) Descriptor() ([]byte, []int) {
|
func (*RemoveDelivery) Descriptor() ([]byte, []int) {
|
||||||
return file_messages_proto_rawDescGZIP(), []int{7}
|
return file_messages_proto_rawDescGZIP(), []int{8}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RemoveDelivery) GetId() int64 {
|
func (x *RemoveDelivery) GetId() int64 {
|
||||||
@@ -613,19 +754,20 @@ func (x *RemoveDelivery) GetId() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateCheckoutOrder struct {
|
type CreateCheckoutOrder struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Terms string `protobuf:"bytes,1,opt,name=terms,proto3" json:"terms,omitempty"`
|
||||||
|
Checkout string `protobuf:"bytes,2,opt,name=checkout,proto3" json:"checkout,omitempty"`
|
||||||
|
Confirmation string `protobuf:"bytes,3,opt,name=confirmation,proto3" json:"confirmation,omitempty"`
|
||||||
|
Push string `protobuf:"bytes,4,opt,name=push,proto3" json:"push,omitempty"`
|
||||||
|
Validation string `protobuf:"bytes,5,opt,name=validation,proto3" json:"validation,omitempty"`
|
||||||
|
Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Terms string `protobuf:"bytes,1,opt,name=Terms,proto3" json:"terms,omitempty"`
|
|
||||||
Checkout string `protobuf:"bytes,2,opt,name=Checkout,proto3" json:"checkout,omitempty"`
|
|
||||||
Confirmation string `protobuf:"bytes,3,opt,name=Confirmation,proto3" json:"confirmation,omitempty"`
|
|
||||||
Push string `protobuf:"bytes,4,opt,name=Push,proto3" json:"push,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateCheckoutOrder) Reset() {
|
func (x *CreateCheckoutOrder) Reset() {
|
||||||
*x = CreateCheckoutOrder{}
|
*x = CreateCheckoutOrder{}
|
||||||
mi := &file_messages_proto_msgTypes[8]
|
mi := &file_messages_proto_msgTypes[9]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -637,7 +779,7 @@ func (x *CreateCheckoutOrder) String() string {
|
|||||||
func (*CreateCheckoutOrder) ProtoMessage() {}
|
func (*CreateCheckoutOrder) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CreateCheckoutOrder) ProtoReflect() protoreflect.Message {
|
func (x *CreateCheckoutOrder) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_messages_proto_msgTypes[8]
|
mi := &file_messages_proto_msgTypes[9]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -650,7 +792,7 @@ func (x *CreateCheckoutOrder) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CreateCheckoutOrder.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CreateCheckoutOrder.ProtoReflect.Descriptor instead.
|
||||||
func (*CreateCheckoutOrder) Descriptor() ([]byte, []int) {
|
func (*CreateCheckoutOrder) Descriptor() ([]byte, []int) {
|
||||||
return file_messages_proto_rawDescGZIP(), []int{8}
|
return file_messages_proto_rawDescGZIP(), []int{9}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateCheckoutOrder) GetTerms() string {
|
func (x *CreateCheckoutOrder) GetTerms() string {
|
||||||
@@ -681,130 +823,217 @@ func (x *CreateCheckoutOrder) GetPush() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *CreateCheckoutOrder) GetValidation() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Validation
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateCheckoutOrder) GetCountry() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Country
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderCreated struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
OrderId string `protobuf:"bytes,1,opt,name=orderId,proto3" json:"orderId,omitempty"`
|
||||||
|
Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OrderCreated) Reset() {
|
||||||
|
*x = OrderCreated{}
|
||||||
|
mi := &file_messages_proto_msgTypes[10]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OrderCreated) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*OrderCreated) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *OrderCreated) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_messages_proto_msgTypes[10]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use OrderCreated.ProtoReflect.Descriptor instead.
|
||||||
|
func (*OrderCreated) Descriptor() ([]byte, []int) {
|
||||||
|
return file_messages_proto_rawDescGZIP(), []int{10}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OrderCreated) GetOrderId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.OrderId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *OrderCreated) GetStatus() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
var File_messages_proto protoreflect.FileDescriptor
|
var File_messages_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_messages_proto_rawDesc = []byte{
|
const file_messages_proto_rawDesc = "" +
|
||||||
0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
"\n" +
|
||||||
0x12, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x3a, 0x0a, 0x0a, 0x41, 0x64,
|
"\x0emessages.proto\x12\bmessages\"\x7f\n" +
|
||||||
0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x51, 0x75, 0x61, 0x6e,
|
"\n" +
|
||||||
0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x51, 0x75, 0x61, 0x6e,
|
"AddRequest\x12\x1a\n" +
|
||||||
0x74, 0x69, 0x74, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x6b, 0x75, 0x18, 0x02, 0x20, 0x01, 0x28,
|
"\bquantity\x18\x01 \x01(\x05R\bquantity\x12\x10\n" +
|
||||||
0x09, 0x52, 0x03, 0x53, 0x6b, 0x75, 0x22, 0xa5, 0x02, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x49, 0x74,
|
"\x03sku\x18\x02 \x01(\tR\x03sku\x12\x18\n" +
|
||||||
0x65, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02,
|
"\acountry\x18\x03 \x01(\tR\acountry\x12\x1d\n" +
|
||||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14,
|
"\astoreId\x18\x04 \x01(\tH\x00R\astoreId\x88\x01\x01B\n" +
|
||||||
0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x50,
|
"\n" +
|
||||||
0x72, 0x69, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4f, 0x72, 0x67, 0x50, 0x72, 0x69, 0x63, 0x65,
|
"\b_storeId\"<\n" +
|
||||||
0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x4f, 0x72, 0x67, 0x50, 0x72, 0x69, 0x63, 0x65,
|
"\x0eSetCartRequest\x12*\n" +
|
||||||
0x12, 0x10, 0x0a, 0x03, 0x53, 0x6b, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x53,
|
"\x05items\x18\x01 \x03(\v2\x14.messages.AddRequestR\x05items\"\xe9\x04\n" +
|
||||||
0x6b, 0x75, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
"\aAddItem\x12\x17\n" +
|
||||||
0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x18,
|
"\aitem_id\x18\x01 \x01(\x03R\x06itemId\x12\x1a\n" +
|
||||||
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
"\bquantity\x18\x02 \x01(\x05R\bquantity\x12\x14\n" +
|
||||||
0x53, 0x74, 0x6f, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x53, 0x74, 0x6f,
|
"\x05price\x18\x03 \x01(\x03R\x05price\x12\x1a\n" +
|
||||||
0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x61, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52,
|
"\borgPrice\x18\t \x01(\x03R\borgPrice\x12\x10\n" +
|
||||||
0x03, 0x54, 0x61, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d,
|
"\x03sku\x18\x04 \x01(\tR\x03sku\x12\x12\n" +
|
||||||
0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x44, 0x69, 0x73, 0x63, 0x6c, 0x61,
|
"\x04name\x18\x05 \x01(\tR\x04name\x12\x14\n" +
|
||||||
0x69, 0x6d, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x41, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, 0x54,
|
"\x05image\x18\x06 \x01(\tR\x05image\x12\x14\n" +
|
||||||
0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x41, 0x72, 0x74, 0x69, 0x63,
|
"\x05stock\x18\a \x01(\x05R\x05stock\x12\x10\n" +
|
||||||
0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x6c, 0x65, 0x74,
|
"\x03tax\x18\b \x01(\x05R\x03tax\x12\x14\n" +
|
||||||
0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x4f, 0x75, 0x74, 0x6c, 0x65, 0x74,
|
"\x05brand\x18\r \x01(\tR\x05brand\x12\x1a\n" +
|
||||||
0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x4f, 0x75, 0x74, 0x6c, 0x65, 0x74, 0x22, 0x1c,
|
"\bcategory\x18\x0e \x01(\tR\bcategory\x12\x1c\n" +
|
||||||
0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x0a, 0x02,
|
"\tcategory2\x18\x0f \x01(\tR\tcategory2\x12\x1c\n" +
|
||||||
0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x49, 0x64, 0x22, 0x3c, 0x0a, 0x0e,
|
"\tcategory3\x18\x10 \x01(\tR\tcategory3\x12\x1c\n" +
|
||||||
0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x0e,
|
"\tcategory4\x18\x11 \x01(\tR\tcategory4\x12\x1c\n" +
|
||||||
0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x49, 0x64, 0x12, 0x1a,
|
"\tcategory5\x18\x12 \x01(\tR\tcategory5\x12\x1e\n" +
|
||||||
0x0a, 0x08, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
|
"\n" +
|
||||||
0x52, 0x08, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x86, 0x02, 0x0a, 0x0b, 0x53,
|
"disclaimer\x18\n" +
|
||||||
0x65, 0x74, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72,
|
" \x01(\tR\n" +
|
||||||
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x72,
|
"disclaimer\x12 \n" +
|
||||||
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x18,
|
"\varticleType\x18\v \x01(\tR\varticleType\x12\x1a\n" +
|
||||||
0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x05, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x3c, 0x0a, 0x0b,
|
"\bsellerId\x18\x13 \x01(\tR\bsellerId\x12\x1e\n" +
|
||||||
0x50, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
|
"\n" +
|
||||||
0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x50, 0x69, 0x63,
|
"sellerName\x18\x14 \x01(\tR\n" +
|
||||||
0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x50, 0x69, 0x63, 0x6b,
|
"sellerName\x12\x18\n" +
|
||||||
0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f,
|
"\acountry\x18\x15 \x01(\tR\acountry\x12\x1b\n" +
|
||||||
0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x6f, 0x75,
|
"\x06outlet\x18\f \x01(\tH\x00R\x06outlet\x88\x01\x01\x12\x1d\n" +
|
||||||
0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x5a, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28,
|
"\astoreId\x18\x16 \x01(\tH\x01R\astoreId\x88\x01\x01B\t\n" +
|
||||||
0x09, 0x52, 0x03, 0x5a, 0x69, 0x70, 0x12, 0x1d, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
|
"\a_outletB\n" +
|
||||||
0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65,
|
"\n" +
|
||||||
0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x43, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20,
|
"\b_storeId\"\x1c\n" +
|
||||||
0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, 0x43, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0e,
|
"\n" +
|
||||||
0x0a, 0x0c, 0x5f, 0x50, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x0a,
|
"RemoveItem\x12\x0e\n" +
|
||||||
0x0a, 0x08, 0x5f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x43,
|
"\x02Id\x18\x01 \x01(\x03R\x02Id\"<\n" +
|
||||||
0x69, 0x74, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x50, 0x69, 0x63, 0x6b, 0x75,
|
"\x0eChangeQuantity\x12\x0e\n" +
|
||||||
0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65,
|
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x1a\n" +
|
||||||
0x72, 0x79, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x44, 0x65, 0x6c, 0x69,
|
"\bquantity\x18\x02 \x01(\x05R\bquantity\"\x86\x02\n" +
|
||||||
0x76, 0x65, 0x72, 0x79, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01,
|
"\vSetDelivery\x12\x1a\n" +
|
||||||
0x28, 0x09, 0x52, 0x02, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03,
|
"\bprovider\x18\x01 \x01(\tR\bprovider\x12\x14\n" +
|
||||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12,
|
"\x05items\x18\x02 \x03(\x03R\x05items\x12<\n" +
|
||||||
0x1d, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
|
"\vpickupPoint\x18\x03 \x01(\v2\x15.messages.PickupPointH\x00R\vpickupPoint\x88\x01\x01\x12\x18\n" +
|
||||||
0x48, 0x01, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17,
|
"\acountry\x18\x04 \x01(\tR\acountry\x12\x10\n" +
|
||||||
0x0a, 0x04, 0x43, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04,
|
"\x03zip\x18\x05 \x01(\tR\x03zip\x12\x1d\n" +
|
||||||
0x43, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x5a, 0x69, 0x70, 0x18, 0x06,
|
"\aaddress\x18\x06 \x01(\tH\x01R\aaddress\x88\x01\x01\x12\x17\n" +
|
||||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x03, 0x5a, 0x69, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1d,
|
"\x04city\x18\a \x01(\tH\x02R\x04city\x88\x01\x01B\x0e\n" +
|
||||||
0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48,
|
"\f_pickupPointB\n" +
|
||||||
0x04, 0x52, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a,
|
"\n" +
|
||||||
0x05, 0x5f, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x41, 0x64, 0x64, 0x72, 0x65,
|
"\b_addressB\a\n" +
|
||||||
0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x43, 0x69, 0x74, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x5f,
|
"\x05_city\"\xf9\x01\n" +
|
||||||
0x5a, 0x69, 0x70, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22,
|
"\x0eSetPickupPoint\x12\x1e\n" +
|
||||||
0xd6, 0x01, 0x0a, 0x0b, 0x50, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12,
|
"\n" +
|
||||||
0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x64, 0x12,
|
"deliveryId\x18\x01 \x01(\x03R\n" +
|
||||||
0x17, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
|
"deliveryId\x12\x0e\n" +
|
||||||
0x04, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72,
|
"\x02id\x18\x02 \x01(\tR\x02id\x12\x17\n" +
|
||||||
0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x41, 0x64, 0x64,
|
"\x04name\x18\x03 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x1d\n" +
|
||||||
0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x43, 0x69, 0x74, 0x79, 0x18,
|
"\aaddress\x18\x04 \x01(\tH\x01R\aaddress\x88\x01\x01\x12\x17\n" +
|
||||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, 0x43, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01,
|
"\x04city\x18\x05 \x01(\tH\x02R\x04city\x88\x01\x01\x12\x15\n" +
|
||||||
0x12, 0x15, 0x0a, 0x03, 0x5a, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52,
|
"\x03zip\x18\x06 \x01(\tH\x03R\x03zip\x88\x01\x01\x12\x1d\n" +
|
||||||
0x03, 0x5a, 0x69, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74,
|
"\acountry\x18\a \x01(\tH\x04R\acountry\x88\x01\x01B\a\n" +
|
||||||
0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x07, 0x43, 0x6f, 0x75, 0x6e,
|
"\x05_nameB\n" +
|
||||||
0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x4e, 0x61, 0x6d, 0x65, 0x42,
|
"\n" +
|
||||||
0x0a, 0x0a, 0x08, 0x5f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f,
|
"\b_addressB\a\n" +
|
||||||
0x43, 0x69, 0x74, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x5a, 0x69, 0x70, 0x42, 0x0a, 0x0a, 0x08,
|
"\x05_cityB\x06\n" +
|
||||||
0x5f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x20, 0x0a, 0x0e, 0x52, 0x65, 0x6d, 0x6f,
|
"\x04_zipB\n" +
|
||||||
0x76, 0x65, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64,
|
"\n" +
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x49, 0x64, 0x22, 0x7f, 0x0a, 0x13, 0x43, 0x72,
|
"\b_country\"\xd6\x01\n" +
|
||||||
0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x4f, 0x72, 0x64, 0x65,
|
"\vPickupPoint\x12\x0e\n" +
|
||||||
0x72, 0x12, 0x14, 0x0a, 0x05, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
"\x02id\x18\x01 \x01(\tR\x02id\x12\x17\n" +
|
||||||
0x52, 0x05, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x68, 0x65, 0x63, 0x6b,
|
"\x04name\x18\x02 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x1d\n" +
|
||||||
0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x68, 0x65, 0x63, 0x6b,
|
"\aaddress\x18\x03 \x01(\tH\x01R\aaddress\x88\x01\x01\x12\x17\n" +
|
||||||
0x6f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74,
|
"\x04city\x18\x04 \x01(\tH\x02R\x04city\x88\x01\x01\x12\x15\n" +
|
||||||
0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
"\x03zip\x18\x05 \x01(\tH\x03R\x03zip\x88\x01\x01\x12\x1d\n" +
|
||||||
0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x75, 0x73, 0x68, 0x18,
|
"\acountry\x18\x06 \x01(\tH\x04R\acountry\x88\x01\x01B\a\n" +
|
||||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x75, 0x73, 0x68, 0x42, 0x0c, 0x5a, 0x0a, 0x2e,
|
"\x05_nameB\n" +
|
||||||
0x3b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
"\n" +
|
||||||
0x33,
|
"\b_addressB\a\n" +
|
||||||
}
|
"\x05_cityB\x06\n" +
|
||||||
|
"\x04_zipB\n" +
|
||||||
|
"\n" +
|
||||||
|
"\b_country\" \n" +
|
||||||
|
"\x0eRemoveDelivery\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\x03R\x02id\"\xb9\x01\n" +
|
||||||
|
"\x13CreateCheckoutOrder\x12\x14\n" +
|
||||||
|
"\x05terms\x18\x01 \x01(\tR\x05terms\x12\x1a\n" +
|
||||||
|
"\bcheckout\x18\x02 \x01(\tR\bcheckout\x12\"\n" +
|
||||||
|
"\fconfirmation\x18\x03 \x01(\tR\fconfirmation\x12\x12\n" +
|
||||||
|
"\x04push\x18\x04 \x01(\tR\x04push\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"validation\x18\x05 \x01(\tR\n" +
|
||||||
|
"validation\x12\x18\n" +
|
||||||
|
"\acountry\x18\x06 \x01(\tR\acountry\"@\n" +
|
||||||
|
"\fOrderCreated\x12\x18\n" +
|
||||||
|
"\aorderId\x18\x01 \x01(\tR\aorderId\x12\x16\n" +
|
||||||
|
"\x06status\x18\x02 \x01(\tR\x06statusB\fZ\n" +
|
||||||
|
".;messagesb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_messages_proto_rawDescOnce sync.Once
|
file_messages_proto_rawDescOnce sync.Once
|
||||||
file_messages_proto_rawDescData = file_messages_proto_rawDesc
|
file_messages_proto_rawDescData []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_messages_proto_rawDescGZIP() []byte {
|
func file_messages_proto_rawDescGZIP() []byte {
|
||||||
file_messages_proto_rawDescOnce.Do(func() {
|
file_messages_proto_rawDescOnce.Do(func() {
|
||||||
file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_proto_rawDescData)
|
file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)))
|
||||||
})
|
})
|
||||||
return file_messages_proto_rawDescData
|
return file_messages_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||||
var file_messages_proto_goTypes = []any{
|
var file_messages_proto_goTypes = []any{
|
||||||
(*AddRequest)(nil), // 0: messages.AddRequest
|
(*AddRequest)(nil), // 0: messages.AddRequest
|
||||||
(*AddItem)(nil), // 1: messages.AddItem
|
(*SetCartRequest)(nil), // 1: messages.SetCartRequest
|
||||||
(*RemoveItem)(nil), // 2: messages.RemoveItem
|
(*AddItem)(nil), // 2: messages.AddItem
|
||||||
(*ChangeQuantity)(nil), // 3: messages.ChangeQuantity
|
(*RemoveItem)(nil), // 3: messages.RemoveItem
|
||||||
(*SetDelivery)(nil), // 4: messages.SetDelivery
|
(*ChangeQuantity)(nil), // 4: messages.ChangeQuantity
|
||||||
(*SetPickupPoint)(nil), // 5: messages.SetPickupPoint
|
(*SetDelivery)(nil), // 5: messages.SetDelivery
|
||||||
(*PickupPoint)(nil), // 6: messages.PickupPoint
|
(*SetPickupPoint)(nil), // 6: messages.SetPickupPoint
|
||||||
(*RemoveDelivery)(nil), // 7: messages.RemoveDelivery
|
(*PickupPoint)(nil), // 7: messages.PickupPoint
|
||||||
(*CreateCheckoutOrder)(nil), // 8: messages.CreateCheckoutOrder
|
(*RemoveDelivery)(nil), // 8: messages.RemoveDelivery
|
||||||
|
(*CreateCheckoutOrder)(nil), // 9: messages.CreateCheckoutOrder
|
||||||
|
(*OrderCreated)(nil), // 10: messages.OrderCreated
|
||||||
}
|
}
|
||||||
var file_messages_proto_depIdxs = []int32{
|
var file_messages_proto_depIdxs = []int32{
|
||||||
6, // 0: messages.SetDelivery.PickupPoint:type_name -> messages.PickupPoint
|
0, // 0: messages.SetCartRequest.items:type_name -> messages.AddRequest
|
||||||
1, // [1:1] is the sub-list for method output_type
|
7, // 1: messages.SetDelivery.pickupPoint:type_name -> messages.PickupPoint
|
||||||
1, // [1:1] is the sub-list for method input_type
|
2, // [2:2] is the sub-list for method output_type
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
2, // [2:2] is the sub-list for method input_type
|
||||||
1, // [1:1] is the sub-list for extension extendee
|
2, // [2:2] is the sub-list for extension type_name
|
||||||
0, // [0:1] is the sub-list for field type_name
|
2, // [2:2] is the sub-list for extension extendee
|
||||||
|
0, // [0:2] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_messages_proto_init() }
|
func init() { file_messages_proto_init() }
|
||||||
@@ -812,17 +1041,18 @@ func file_messages_proto_init() {
|
|||||||
if File_messages_proto != nil {
|
if File_messages_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
file_messages_proto_msgTypes[1].OneofWrappers = []any{}
|
file_messages_proto_msgTypes[0].OneofWrappers = []any{}
|
||||||
file_messages_proto_msgTypes[4].OneofWrappers = []any{}
|
file_messages_proto_msgTypes[2].OneofWrappers = []any{}
|
||||||
file_messages_proto_msgTypes[5].OneofWrappers = []any{}
|
file_messages_proto_msgTypes[5].OneofWrappers = []any{}
|
||||||
file_messages_proto_msgTypes[6].OneofWrappers = []any{}
|
file_messages_proto_msgTypes[6].OneofWrappers = []any{}
|
||||||
|
file_messages_proto_msgTypes[7].OneofWrappers = []any{}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_messages_proto_rawDesc,
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 9,
|
NumMessages: 11,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
@@ -831,7 +1061,6 @@ func file_messages_proto_init() {
|
|||||||
MessageInfos: file_messages_proto_msgTypes,
|
MessageInfos: file_messages_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_messages_proto = out.File
|
File_messages_proto = out.File
|
||||||
file_messages_proto_rawDesc = nil
|
|
||||||
file_messages_proto_goTypes = nil
|
file_messages_proto_goTypes = nil
|
||||||
file_messages_proto_depIdxs = nil
|
file_messages_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,39 @@ package messages;
|
|||||||
option go_package = ".;messages";
|
option go_package = ".;messages";
|
||||||
|
|
||||||
message AddRequest {
|
message AddRequest {
|
||||||
int32 Quantity = 1;
|
int32 quantity = 1;
|
||||||
string Sku = 2;
|
string sku = 2;
|
||||||
|
string country = 3;
|
||||||
|
optional string storeId = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetCartRequest {
|
||||||
|
repeated AddRequest items = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddItem {
|
message AddItem {
|
||||||
int32 Quantity = 2;
|
int64 item_id = 1;
|
||||||
int64 Price = 3;
|
int32 quantity = 2;
|
||||||
int64 OrgPrice = 9;
|
int64 price = 3;
|
||||||
string Sku = 4;
|
int64 orgPrice = 9;
|
||||||
string Name = 5;
|
string sku = 4;
|
||||||
string Image = 6;
|
string name = 5;
|
||||||
int32 Stock = 7;
|
string image = 6;
|
||||||
int32 Tax = 8;
|
int32 stock = 7;
|
||||||
string Disclaimer = 10;
|
int32 tax = 8;
|
||||||
string ArticleType = 11;
|
string brand = 13;
|
||||||
optional string Outlet = 12;
|
string category = 14;
|
||||||
|
string category2 = 15;
|
||||||
|
string category3 = 16;
|
||||||
|
string category4 = 17;
|
||||||
|
string category5 = 18;
|
||||||
|
string disclaimer = 10;
|
||||||
|
string articleType = 11;
|
||||||
|
string sellerId = 19;
|
||||||
|
string sellerName = 20;
|
||||||
|
string country = 21;
|
||||||
|
optional string outlet = 12;
|
||||||
|
optional string storeId = 22;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RemoveItem {
|
message RemoveItem {
|
||||||
@@ -26,46 +43,53 @@ message RemoveItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message ChangeQuantity {
|
message ChangeQuantity {
|
||||||
int64 Id = 1;
|
int64 id = 1;
|
||||||
int32 Quantity = 2;
|
int32 quantity = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetDelivery {
|
message SetDelivery {
|
||||||
string Provider = 1;
|
string provider = 1;
|
||||||
repeated int64 Items = 2;
|
repeated int64 items = 2;
|
||||||
optional PickupPoint PickupPoint = 3;
|
optional PickupPoint pickupPoint = 3;
|
||||||
string Country = 4;
|
string country = 4;
|
||||||
string Zip = 5;
|
string zip = 5;
|
||||||
optional string Address = 6;
|
optional string address = 6;
|
||||||
optional string City = 7;
|
optional string city = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetPickupPoint {
|
message SetPickupPoint {
|
||||||
int64 DeliveryId = 1;
|
int64 deliveryId = 1;
|
||||||
string Id = 2;
|
string id = 2;
|
||||||
optional string Name = 3;
|
optional string name = 3;
|
||||||
optional string Address = 4;
|
optional string address = 4;
|
||||||
optional string City = 5;
|
optional string city = 5;
|
||||||
optional string Zip = 6;
|
optional string zip = 6;
|
||||||
optional string Country = 7;
|
optional string country = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PickupPoint {
|
message PickupPoint {
|
||||||
string Id = 1;
|
string id = 1;
|
||||||
optional string Name = 2;
|
optional string name = 2;
|
||||||
optional string Address = 3;
|
optional string address = 3;
|
||||||
optional string City = 4;
|
optional string city = 4;
|
||||||
optional string Zip = 5;
|
optional string zip = 5;
|
||||||
optional string Country = 6;
|
optional string country = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RemoveDelivery {
|
message RemoveDelivery {
|
||||||
int64 Id = 1;
|
int64 id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateCheckoutOrder {
|
message CreateCheckoutOrder {
|
||||||
string Terms = 1;
|
string terms = 1;
|
||||||
string Checkout = 2;
|
string checkout = 2;
|
||||||
string Confirmation = 3;
|
string confirmation = 3;
|
||||||
string Push = 4;
|
string push = 4;
|
||||||
|
string validation = 5;
|
||||||
|
string country = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OrderCreated {
|
||||||
|
string orderId = 1;
|
||||||
|
string status = 2;
|
||||||
}
|
}
|
||||||
67
remote-grain-pool._go
Normal file
67
remote-grain-pool._go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// package main
|
||||||
|
|
||||||
|
// import "sync"
|
||||||
|
|
||||||
|
// type RemoteGrainPool struct {
|
||||||
|
// mu sync.RWMutex
|
||||||
|
// Host string
|
||||||
|
// grains map[CartId]*RemoteGrain
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func NewRemoteGrainPool(addr string) *RemoteGrainPool {
|
||||||
|
// return &RemoteGrainPool{
|
||||||
|
// Host: addr,
|
||||||
|
// grains: make(map[CartId]*RemoteGrain),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (p *RemoteGrainPool) findRemoteGrain(id CartId) *RemoteGrain {
|
||||||
|
// p.mu.RLock()
|
||||||
|
// grain, ok := p.grains[id]
|
||||||
|
// p.mu.RUnlock()
|
||||||
|
// if !ok {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// return grain
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (p *RemoteGrainPool) findOrCreateGrain(id CartId) (*RemoteGrain, error) {
|
||||||
|
// grain := p.findRemoteGrain(id)
|
||||||
|
|
||||||
|
// if grain == nil {
|
||||||
|
// grain, err := NewRemoteGrain(id, p.Host)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// p.mu.Lock()
|
||||||
|
// p.grains[id] = grain
|
||||||
|
// p.mu.Unlock()
|
||||||
|
// }
|
||||||
|
// return grain, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (p *RemoteGrainPool) Delete(id CartId) {
|
||||||
|
// p.mu.Lock()
|
||||||
|
// delete(p.grains, id)
|
||||||
|
// p.mu.Unlock()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (p *RemoteGrainPool) Process(id CartId, messages ...Message) (*FrameWithPayload, error) {
|
||||||
|
// var result *FrameWithPayload
|
||||||
|
// grain, err := p.findOrCreateGrain(id)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// for _, message := range messages {
|
||||||
|
// result, err = grain.HandleMessage(&message, false)
|
||||||
|
// }
|
||||||
|
// return result, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (p *RemoteGrainPool) Get(id CartId) (*FrameWithPayload, error) {
|
||||||
|
// grain, err := p.findOrCreateGrain(id)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// return grain.GetCurrentState()
|
||||||
|
// }
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
type RemoteGrainPool struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
Host string
|
|
||||||
grains map[CartId]*RemoteGrain
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRemoteGrainPool(addr string) *RemoteGrainPool {
|
|
||||||
return &RemoteGrainPool{
|
|
||||||
Host: addr,
|
|
||||||
grains: make(map[CartId]*RemoteGrain),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RemoteGrainPool) findRemoteGrain(id CartId) *RemoteGrain {
|
|
||||||
p.mu.RLock()
|
|
||||||
grain, ok := p.grains[id]
|
|
||||||
p.mu.RUnlock()
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return grain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RemoteGrainPool) findOrCreateGrain(id CartId) (*RemoteGrain, error) {
|
|
||||||
grain := p.findRemoteGrain(id)
|
|
||||||
|
|
||||||
if grain == nil {
|
|
||||||
grain, err := NewRemoteGrain(id, p.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.mu.Lock()
|
|
||||||
p.grains[id] = grain
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
return grain, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RemoteGrainPool) Delete(id CartId) {
|
|
||||||
p.mu.Lock()
|
|
||||||
delete(p.grains, id)
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RemoteGrainPool) Process(id CartId, messages ...Message) (*FrameWithPayload, error) {
|
|
||||||
var result *FrameWithPayload
|
|
||||||
grain, err := p.findOrCreateGrain(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, message := range messages {
|
|
||||||
result, err = grain.HandleMessage(&message, false)
|
|
||||||
}
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RemoteGrainPool) Get(id CartId) (*FrameWithPayload, error) {
|
|
||||||
grain, err := p.findOrCreateGrain(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return grain.GetCurrentState()
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (id CartId) String() string {
|
|
||||||
return strings.Trim(string(id[:]), "\x00")
|
|
||||||
}
|
|
||||||
|
|
||||||
type CartIdPayload struct {
|
|
||||||
Id CartId
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeCartInnerFrame(id CartId, payload []byte) []byte {
|
|
||||||
if payload == nil {
|
|
||||||
return id[:]
|
|
||||||
}
|
|
||||||
return append(id[:], payload...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCartFrame(data []byte) (*CartIdPayload, error) {
|
|
||||||
if len(data) < 16 {
|
|
||||||
return nil, fmt.Errorf("data too short")
|
|
||||||
}
|
|
||||||
return &CartIdPayload{
|
|
||||||
Id: CartId(data[:16]),
|
|
||||||
Data: data[16:],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToCartId(id string) CartId {
|
|
||||||
var result [16]byte
|
|
||||||
copy(result[:], []byte(id))
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoteGrain struct {
|
|
||||||
*Connection
|
|
||||||
Id CartId
|
|
||||||
Host string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRemoteGrain(id CartId, host string) (*RemoteGrain, error) {
|
|
||||||
return &RemoteGrain{
|
|
||||||
Id: id,
|
|
||||||
Host: host,
|
|
||||||
Connection: NewConnection(fmt.Sprintf("%s:1337", host)),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
remoteCartLatency = promauto.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "cart_remote_grain_calls_total_latency",
|
|
||||||
Help: "The total latency of remote grains",
|
|
||||||
})
|
|
||||||
remoteCartCallsTotal = promauto.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "cart_remote_grain_calls_total",
|
|
||||||
Help: "The total number of calls to remote grains",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// var start time.Time
|
|
||||||
|
|
||||||
// func MeasureLatency(fn func() (*CallResult, error)) (*CallResult, error) {
|
|
||||||
// start = time.Now()
|
|
||||||
// data, err := fn()
|
|
||||||
// if err != nil {
|
|
||||||
// return data, err
|
|
||||||
// }
|
|
||||||
// elapsed := time.Since(start).Milliseconds()
|
|
||||||
// go func() {
|
|
||||||
// remoteCartLatency.Add(float64(elapsed))
|
|
||||||
// remoteCartCallsTotal.Inc()
|
|
||||||
// }()
|
|
||||||
// return data, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (g *RemoteGrain) HandleMessage(message *Message, isReplay bool) (*FrameWithPayload, error) {
|
|
||||||
|
|
||||||
data, err := GetData(message.Write)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return g.Call(RemoteHandleMutation, MakeCartInnerFrame(g.Id, data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *RemoteGrain) GetId() CartId {
|
|
||||||
return g.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *RemoteGrain) GetCurrentState() (*FrameWithPayload, error) {
|
|
||||||
return g.Call(RemoteGetState, MakeCartInnerFrame(g.Id, nil))
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestCartIdsNullData(t *testing.T) {
|
|
||||||
data := MakeCartInnerFrame(ToCartId("kalle"), nil)
|
|
||||||
cart, err := GetCartFrame(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error getting cart: %v", err)
|
|
||||||
}
|
|
||||||
if cart.Id.String() != "kalle" {
|
|
||||||
t.Errorf("Expected kalle, got %s", cart.Id)
|
|
||||||
}
|
|
||||||
if len(cart.Data) != 0 {
|
|
||||||
t.Errorf("Expected no data, got %v", cart.Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RemoteHost struct {
|
|
||||||
*Connection
|
|
||||||
Host string
|
|
||||||
MissedPings int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RemoteHost) IsHealthy() bool {
|
|
||||||
return h.MissedPings < 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RemoteHost) Initialize(p *SyncedPool) {
|
|
||||||
log.Printf("Initializing remote %s\n", h.Host)
|
|
||||||
ids, err := h.GetCartMappings()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error getting remote mappings: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("Remote %s has %d grains\n", h.Host, len(ids))
|
|
||||||
p.mu.Lock()
|
|
||||||
local := 0
|
|
||||||
remoteNo := 0
|
|
||||||
for _, id := range ids {
|
|
||||||
go p.SpawnRemoteGrain(id, h.Host)
|
|
||||||
remoteNo++
|
|
||||||
}
|
|
||||||
log.Printf("Removed %d local grains, added %d remote grains\n", local, remoteNo)
|
|
||||||
p.mu.Unlock()
|
|
||||||
go p.Negotiate()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RemoteHost) Ping() error {
|
|
||||||
result, err := h.Call(Ping, nil)
|
|
||||||
|
|
||||||
if err != nil || result.StatusCode != 200 || result.Type != Pong {
|
|
||||||
h.MissedPings++
|
|
||||||
log.Printf("Error pinging remote %s, missed pings: %d", h.Host, h.MissedPings)
|
|
||||||
} else {
|
|
||||||
h.MissedPings = 0
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *RemoteHost) Negotiate(knownHosts []string) ([]string, error) {
|
|
||||||
reply, err := h.Call(RemoteNegotiate, []byte(strings.Join(knownHosts, ";")))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if reply.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("remote returned error on negotiate: %s", string(reply.Payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Split(string(reply.Payload), ";"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *RemoteHost) GetCartMappings() ([]CartId, error) {
|
|
||||||
reply, err := g.Call(GetCartIds, []byte{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if reply.StatusCode != 200 || reply.Type != CartIdsResponse {
|
|
||||||
log.Printf("Remote returned error on get cart mappings: %s", string(reply.Payload))
|
|
||||||
return nil, fmt.Errorf("remote returned incorrect data")
|
|
||||||
}
|
|
||||||
parts := strings.Split(string(reply.Payload), ";")
|
|
||||||
ids := make([]CartId, 0, len(parts))
|
|
||||||
for _, p := range parts {
|
|
||||||
ids = append(ids, ToCartId(p))
|
|
||||||
}
|
|
||||||
return ids, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RemoteHost) ConfirmChange(id CartId, host string) error {
|
|
||||||
reply, err := r.Call(RemoteGrainChanged, []byte(fmt.Sprintf("%s;%s", id, host)))
|
|
||||||
|
|
||||||
if err != nil || reply.StatusCode != 200 || reply.Type != AckChange {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
147
remote_grain_grpc.go
Normal file
147
remote_grain_grpc.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
proto "git.tornberg.me/go-cart-actor/proto" // generated package name is 'messages'; aliased as proto for consistency
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RemoteGrainGRPC is the gRPC-backed implementation of a remote grain.
|
||||||
|
// It mirrors the previous RemoteGrain (TCP/frame based) while using the
|
||||||
|
// new CartActor gRPC service. It implements the Grain interface so that
|
||||||
|
// SyncedPool can remain largely unchanged when swapping transport layers.
|
||||||
|
type RemoteGrainGRPC struct {
|
||||||
|
Id CartId
|
||||||
|
Host string
|
||||||
|
client proto.CartActorClient
|
||||||
|
// Optional: keep the underlying conn so higher-level code can close if needed
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
|
||||||
|
// Per-call timeout settings (tunable)
|
||||||
|
mutateTimeout time.Duration
|
||||||
|
stateTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRemoteGrainGRPC constructs a remote grain adapter from an existing gRPC client.
|
||||||
|
func NewRemoteGrainGRPC(id CartId, host string, client proto.CartActorClient) *RemoteGrainGRPC {
|
||||||
|
return &RemoteGrainGRPC{
|
||||||
|
Id: id,
|
||||||
|
Host: host,
|
||||||
|
client: client,
|
||||||
|
mutateTimeout: 800 * time.Millisecond,
|
||||||
|
stateTimeout: 400 * time.Millisecond,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRemoteGrainGRPCWithConn dials the target and creates the gRPC client.
|
||||||
|
// target should be host:port (where the CartActor service is exposed).
|
||||||
|
func NewRemoteGrainGRPCWithConn(id CartId, host string, target string, dialOpts ...grpc.DialOption) (*RemoteGrainGRPC, error) {
|
||||||
|
// NOTE: insecure for initial migration; should be replaced with TLS later.
|
||||||
|
baseOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithBlock()}
|
||||||
|
baseOpts = append(baseOpts, dialOpts...)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
conn, err := grpc.DialContext(ctx, target, baseOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := proto.NewCartActorClient(conn)
|
||||||
|
return &RemoteGrainGRPC{
|
||||||
|
Id: id,
|
||||||
|
Host: host,
|
||||||
|
client: client,
|
||||||
|
conn: conn,
|
||||||
|
mutateTimeout: 800 * time.Millisecond,
|
||||||
|
stateTimeout: 400 * time.Millisecond,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *RemoteGrainGRPC) GetId() CartId {
|
||||||
|
return g.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleMessage serializes the underlying mutation proto (without legacy message header)
|
||||||
|
// and invokes the CartActor.Mutate RPC. It wraps the reply into a FrameWithPayload
|
||||||
|
// for compatibility with existing higher-level code paths.
|
||||||
|
func (g *RemoteGrainGRPC) HandleMessage(message *Message, isReplay bool) (*FrameWithPayload, error) {
|
||||||
|
if message == nil {
|
||||||
|
return nil, fmt.Errorf("nil message")
|
||||||
|
}
|
||||||
|
if isReplay {
|
||||||
|
// Remote replay not expected; ignore to keep parity with old implementation.
|
||||||
|
return nil, fmt.Errorf("replay not supported for remote grains")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler, err := GetMessageHandler(message.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure timestamp set (legacy behavior)
|
||||||
|
if message.TimeStamp == nil {
|
||||||
|
ts := time.Now().Unix()
|
||||||
|
message.TimeStamp = &ts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal underlying proto payload only (no StorableMessageHeader)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = handler.Write(message, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("encode mutation payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &proto.MutationRequest{
|
||||||
|
CartId: g.Id.String(),
|
||||||
|
Type: proto.MutationType(message.Type), // numeric mapping preserved
|
||||||
|
Payload: buf.Bytes(),
|
||||||
|
ClientTimestamp: *message.TimeStamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), g.mutateTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := g.client.Mutate(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
frame := MakeFrameWithPayload(RemoteHandleMutationReply, StatusCode(resp.StatusCode), resp.Payload)
|
||||||
|
return &frame, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentState calls CartActor.GetState and returns a FrameWithPayload
|
||||||
|
// shaped like the legacy RemoteGetStateReply.
|
||||||
|
func (g *RemoteGrainGRPC) GetCurrentState() (*FrameWithPayload, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), g.stateTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := g.client.GetState(ctx, &proto.StateRequest{
|
||||||
|
CartId: g.Id.String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
frame := MakeFrameWithPayload(RemoteGetStateReply, StatusCode(resp.StatusCode), resp.Payload)
|
||||||
|
return &frame, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the underlying gRPC connection if this adapter created it.
|
||||||
|
func (g *RemoteGrainGRPC) Close() error {
|
||||||
|
if g.conn != nil {
|
||||||
|
return g.conn.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug helper to log operations (optional).
|
||||||
|
func (g *RemoteGrainGRPC) logf(format string, args ...interface{}) {
|
||||||
|
log.Printf("[remote-grain-grpc host=%s id=%s] %s", g.Host, g.Id.String(), fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GrainHandler struct {
|
|
||||||
*GenericListener
|
|
||||||
pool *GrainLocalPool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *GrainHandler) GetState(id CartId, reply *Grain) error {
|
|
||||||
grain, err := h.pool.GetGrain(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*reply = grain
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGrainHandler(pool *GrainLocalPool, listen string) (*GrainHandler, error) {
|
|
||||||
conn := NewConnection(listen)
|
|
||||||
server, err := conn.Listen()
|
|
||||||
handler := &GrainHandler{
|
|
||||||
GenericListener: server,
|
|
||||||
pool: pool,
|
|
||||||
}
|
|
||||||
server.AddHandler(RemoteHandleMutation, handler.RemoteHandleMessageHandler)
|
|
||||||
server.AddHandler(RemoteGetState, handler.RemoteGetStateHandler)
|
|
||||||
return handler, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *GrainHandler) IsHealthy() bool {
|
|
||||||
return len(h.pool.grains) < h.pool.PoolSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *GrainHandler) RemoteHandleMessageHandler(data *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
cartData, err := GetCartFrame(data.Payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var msg Message
|
|
||||||
err = ReadMessage(bytes.NewReader(cartData.Data), &msg)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error reading message:", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
replyData, err := h.pool.Process(cartData.Id, msg)
|
|
||||||
|
|
||||||
resultChan <- *replyData
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *GrainHandler) RemoteGetStateHandler(data *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
cartData, err := GetCartFrame(data.Payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reply, err := h.pool.Get(cartData.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resultChan <- *reply
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
664
synced-pool.go
664
synced-pool.go
@@ -1,34 +1,59 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
proto "git.tornberg.me/go-cart-actor/proto"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
"google.golang.org/grpc"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Quorum interface {
|
// SyncedPool coordinates cart grain ownership across nodes using gRPC control plane
|
||||||
Negotiate(knownHosts []string) ([]string, error)
|
// and cart actor services. Legacy frame / TCP code has been removed.
|
||||||
OwnerChanged(CartId, host string) error
|
//
|
||||||
}
|
// Responsibilities:
|
||||||
|
// - Local grain access (delegates to GrainLocalPool)
|
||||||
type HealthHandler interface {
|
// - Remote grain proxy management (RemoteGrainGRPC)
|
||||||
IsHealthy() bool
|
// - Cluster membership (AddRemote via discovery + negotiation)
|
||||||
}
|
// - Ownership acquisition (quorum via ConfirmOwner RPC)
|
||||||
|
// - Health/ping monitoring & remote removal
|
||||||
|
//
|
||||||
|
// Thread-safety: public methods that mutate internal maps lock p.mu (RWMutex).
|
||||||
type SyncedPool struct {
|
type SyncedPool struct {
|
||||||
Server *GenericListener
|
Hostname string
|
||||||
mu sync.RWMutex
|
local *GrainLocalPool
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
// Remote host state (gRPC only)
|
||||||
|
remoteHosts map[string]*RemoteHostGRPC // host -> remote host
|
||||||
|
|
||||||
|
// Remote grain proxies (by cart id)
|
||||||
|
remoteIndex map[CartId]Grain
|
||||||
|
|
||||||
|
// Discovery handler for re-adding hosts after failures
|
||||||
discardedHostHandler *DiscardedHostHandler
|
discardedHostHandler *DiscardedHostHandler
|
||||||
Hostname string
|
|
||||||
local *GrainLocalPool
|
// Metrics / instrumentation dependencies already declared globally
|
||||||
remotes map[string]*RemoteHost
|
}
|
||||||
remoteIndex map[CartId]*RemoteGrain
|
|
||||||
|
// RemoteHostGRPC tracks a remote host's clients & health.
|
||||||
|
type RemoteHostGRPC struct {
|
||||||
|
Host string
|
||||||
|
Conn *grpc.ClientConn
|
||||||
|
CartClient proto.CartActorClient
|
||||||
|
ControlClient proto.ControlPlaneClient
|
||||||
|
MissedPings int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteHostGRPC) IsHealthy() bool {
|
||||||
|
return r.MissedPings < 3
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -48,206 +73,172 @@ var (
|
|||||||
Name: "cart_remote_lookup_total",
|
Name: "cart_remote_lookup_total",
|
||||||
Help: "The total number of remote lookups",
|
Help: "The total number of remote lookups",
|
||||||
})
|
})
|
||||||
packetQueue = promauto.NewGauge(prometheus.GaugeOpts{
|
|
||||||
Name: "cart_packet_queue_size",
|
|
||||||
Help: "The total number of packets in the queue",
|
|
||||||
})
|
|
||||||
packetsSent = promauto.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "cart_pool_packets_sent_total",
|
|
||||||
Help: "The total number of packets sent",
|
|
||||||
})
|
|
||||||
packetsReceived = promauto.NewCounter(prometheus.CounterOpts{
|
|
||||||
Name: "cart_pool_packets_received_total",
|
|
||||||
Help: "The total number of packets received",
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *SyncedPool) PongHandler(data *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
resultChan <- MakeFrameWithPayload(Pong, 200, []byte{})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) GetCartIdHandler(data *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
ids := make([]string, 0, len(p.local.grains))
|
|
||||||
for id := range p.local.grains {
|
|
||||||
if p.local.grains[id] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s := id.String()
|
|
||||||
if s == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ids = append(ids, s)
|
|
||||||
}
|
|
||||||
log.Printf("Returning %d cart ids\n", len(ids))
|
|
||||||
resultChan <- MakeFrameWithPayload(CartIdsResponse, 200, []byte(strings.Join(ids, ";")))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) NegotiateHandler(data *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
negotiationCount.Inc()
|
|
||||||
log.Printf("Handling negotiation\n")
|
|
||||||
for _, host := range p.ExcludeKnown(strings.Split(string(data.Payload), ";")) {
|
|
||||||
if host == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go p.AddRemote(host)
|
|
||||||
|
|
||||||
}
|
|
||||||
p.mu.RLock()
|
|
||||||
defer p.mu.RUnlock()
|
|
||||||
hosts := make([]string, 0, len(p.remotes))
|
|
||||||
for _, r := range p.remotes {
|
|
||||||
if r.IsHealthy() {
|
|
||||||
hosts = append(hosts, r.Host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resultChan <- MakeFrameWithPayload(RemoteNegotiateResponse, 200, []byte(strings.Join(hosts, ";")))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) GrainOwnerChangeHandler(data *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
grainSyncCount.Inc()
|
|
||||||
|
|
||||||
idAndHostParts := strings.Split(string(data.Payload), ";")
|
|
||||||
if len(idAndHostParts) != 2 {
|
|
||||||
log.Printf("Invalid remote grain change message")
|
|
||||||
resultChan <- MakeFrameWithPayload(AckError, 500, []byte("invalid"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
id := ToCartId(idAndHostParts[0])
|
|
||||||
host := idAndHostParts[1]
|
|
||||||
log.Printf("Handling remote grain owner change to %s for id %s", host, id)
|
|
||||||
for _, r := range p.remotes {
|
|
||||||
if r.Host == host && r.IsHealthy() {
|
|
||||||
go p.SpawnRemoteGrain(id, host)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go p.AddRemote(host)
|
|
||||||
resultChan <- MakeFrameWithPayload(AckChange, 200, []byte("ok"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) RemoveRemoteGrain(id CartId) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
delete(p.remoteIndex, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) SpawnRemoteGrain(id CartId, host string) {
|
|
||||||
if id.String() == "" {
|
|
||||||
log.Printf("Invalid grain id, %s", id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.local.grains[id] != nil {
|
|
||||||
log.Printf("Grain %s already exists locally, owner is (%s)", id, host)
|
|
||||||
p.mu.Lock()
|
|
||||||
delete(p.local.grains, id)
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
remote, err := NewRemoteGrain(id, host)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error creating remote grain %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
p.remoteIndex[id] = remote
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) HandleHostError(host string) {
|
|
||||||
for _, r := range p.remotes {
|
|
||||||
if r.Host == host {
|
|
||||||
if !r.IsHealthy() {
|
|
||||||
p.RemoveHost(r)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSyncedPool(local *GrainLocalPool, hostname string, discovery Discovery) (*SyncedPool, error) {
|
func NewSyncedPool(local *GrainLocalPool, hostname string, discovery Discovery) (*SyncedPool, error) {
|
||||||
listen := fmt.Sprintf("%s:1338", hostname)
|
p := &SyncedPool{
|
||||||
conn := NewConnection(listen)
|
|
||||||
server, err := conn.Listen()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Listening on %s", listen)
|
|
||||||
dh := NewDiscardedHostHandler(1338)
|
|
||||||
pool := &SyncedPool{
|
|
||||||
Server: server,
|
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
local: local,
|
local: local,
|
||||||
discardedHostHandler: dh,
|
remoteHosts: make(map[string]*RemoteHostGRPC),
|
||||||
remotes: make(map[string]*RemoteHost),
|
remoteIndex: make(map[CartId]Grain),
|
||||||
remoteIndex: make(map[CartId]*RemoteGrain),
|
discardedHostHandler: NewDiscardedHostHandler(1338),
|
||||||
}
|
}
|
||||||
dh.SetReconnectHandler(pool.AddRemote)
|
p.discardedHostHandler.SetReconnectHandler(p.AddRemote)
|
||||||
server.AddHandler(Ping, pool.PongHandler)
|
|
||||||
server.AddHandler(GetCartIds, pool.GetCartIdHandler)
|
|
||||||
server.AddHandler(RemoteNegotiate, pool.NegotiateHandler)
|
|
||||||
server.AddHandler(RemoteGrainChanged, pool.GrainOwnerChangeHandler)
|
|
||||||
|
|
||||||
if discovery != nil {
|
if discovery != nil {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second * 5)
|
time.Sleep(3 * time.Second) // allow gRPC server startup
|
||||||
log.Printf("Starting discovery")
|
log.Printf("Starting discovery watcher")
|
||||||
ch, err := discovery.Watch()
|
ch, err := discovery.Watch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error discovering hosts: %v", err)
|
log.Printf("Discovery error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for chng := range ch {
|
for evt := range ch {
|
||||||
if chng.Host == "" {
|
if evt.Host == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
known := pool.IsKnown(chng.Host)
|
switch evt.Type {
|
||||||
if chng.Type != watch.Deleted && !known {
|
case watch.Deleted:
|
||||||
|
if p.IsKnown(evt.Host) {
|
||||||
log.Printf("Discovered host %s, waiting for startup", chng.Host)
|
p.RemoveHost(evt.Host)
|
||||||
time.Sleep(3 * time.Second)
|
}
|
||||||
pool.AddRemote(chng.Host)
|
default:
|
||||||
|
if !p.IsKnown(evt.Host) {
|
||||||
} else if chng.Type == watch.Deleted && known {
|
log.Printf("Discovered host %s", evt.Host)
|
||||||
log.Printf("Host removed %s, removing from index", chng.Host)
|
p.AddRemote(evt.Host)
|
||||||
for _, r := range pool.remotes {
|
|
||||||
if r.Host == chng.Host {
|
|
||||||
pool.RemoveHost(r)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
log.Printf("No discovery, waiting for remotes to connect")
|
log.Printf("No discovery configured; expecting manual AddRemote or static host injection")
|
||||||
}
|
}
|
||||||
|
|
||||||
return pool, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SyncedPool) IsHealthy() bool {
|
// ------------------------- Remote Host Management -----------------------------
|
||||||
for _, r := range p.remotes {
|
|
||||||
if !r.IsHealthy() {
|
// AddRemote dials a remote host and initializes grain proxies.
|
||||||
return false
|
func (p *SyncedPool) AddRemote(host string) {
|
||||||
|
if host == "" || host == p.Hostname {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
if _, exists := p.remoteHosts[host]; exists {
|
||||||
|
p.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
target := fmt.Sprintf("%s:1337", host)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
conn, err := grpc.DialContext(ctx, target, grpc.WithInsecure(), grpc.WithBlock())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("AddRemote: dial %s failed: %v", target, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cartClient := proto.NewCartActorClient(conn)
|
||||||
|
controlClient := proto.NewControlPlaneClient(conn)
|
||||||
|
|
||||||
|
// Health check (Ping) with limited retries
|
||||||
|
pings := 3
|
||||||
|
for pings > 0 {
|
||||||
|
ctxPing, cancelPing := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
_, pingErr := controlClient.Ping(ctxPing, &proto.Empty{})
|
||||||
|
cancelPing()
|
||||||
|
if pingErr == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pings--
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
if pings == 0 {
|
||||||
|
log.Printf("AddRemote: ping %s failed after retries: %v", host, pingErr)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
remote := &RemoteHostGRPC{
|
||||||
|
Host: host,
|
||||||
|
Conn: conn,
|
||||||
|
CartClient: cartClient,
|
||||||
|
ControlClient: controlClient,
|
||||||
|
MissedPings: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
p.remoteHosts[host] = remote
|
||||||
|
p.mu.Unlock()
|
||||||
|
connectedRemotes.Set(float64(p.RemoteCount()))
|
||||||
|
|
||||||
|
log.Printf("Connected to remote host %s", host)
|
||||||
|
|
||||||
|
go p.pingLoop(remote)
|
||||||
|
go p.initializeRemote(remote)
|
||||||
|
go p.Negotiate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeRemote fetches remote cart ids and sets up remote grain proxies.
|
||||||
|
func (p *SyncedPool) initializeRemote(remote *RemoteHostGRPC) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
reply, err := remote.ControlClient.GetCartIds(ctx, &proto.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Init remote %s: GetCartIds error: %v", remote.Host, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
for _, idStr := range reply.CartIds {
|
||||||
|
if idStr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.SpawnRemoteGrain(ToCartId(idStr), remote.Host)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
log.Printf("Remote %s reported %d grains", remote.Host, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveHost removes remote host and its grains.
|
||||||
|
func (p *SyncedPool) RemoveHost(host string) {
|
||||||
|
p.mu.Lock()
|
||||||
|
remote, exists := p.remoteHosts[host]
|
||||||
|
if exists {
|
||||||
|
delete(p.remoteHosts, host)
|
||||||
|
}
|
||||||
|
// remove grains pointing to host
|
||||||
|
for id, g := range p.remoteIndex {
|
||||||
|
if rg, ok := g.(*RemoteGrainGRPC); ok && rg.Host == host {
|
||||||
|
delete(p.remoteIndex, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
remote.Conn.Close()
|
||||||
|
}
|
||||||
|
connectedRemotes.Set(float64(p.RemoteCount()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteCount returns number of tracked remote hosts.
|
||||||
|
func (p *SyncedPool) RemoteCount() int {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
return len(p.remoteHosts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SyncedPool) IsKnown(host string) bool {
|
func (p *SyncedPool) IsKnown(host string) bool {
|
||||||
for _, r := range p.remotes {
|
if host == p.Hostname {
|
||||||
if r.Host == host {
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
p.mu.RLock()
|
||||||
return host == p.Hostname
|
defer p.mu.RUnlock()
|
||||||
|
_, ok := p.remoteHosts[host]
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SyncedPool) ExcludeKnown(hosts []string) []string {
|
func (p *SyncedPool) ExcludeKnown(hosts []string) []string {
|
||||||
@@ -260,202 +251,190 @@ func (p *SyncedPool) ExcludeKnown(hosts []string) []string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SyncedPool) RemoveHost(host *RemoteHost) {
|
// ------------------------- Health / Ping -------------------------------------
|
||||||
p.mu.Lock()
|
|
||||||
delete(p.remotes, host.Host)
|
|
||||||
p.mu.Unlock()
|
|
||||||
p.RemoveHostMappedCarts(host)
|
|
||||||
p.discardedHostHandler.AppendHost(host.Host)
|
|
||||||
connectedRemotes.Set(float64(len(p.remotes)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) RemoveHostMappedCarts(host *RemoteHost) {
|
func (p *SyncedPool) pingLoop(remote *RemoteHostGRPC) {
|
||||||
p.mu.Lock()
|
ticker := time.NewTicker(3 * time.Second)
|
||||||
defer p.mu.Unlock()
|
defer ticker.Stop()
|
||||||
for id, r := range p.remoteIndex {
|
for range ticker.C {
|
||||||
if r.Host == host.Host {
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
delete(p.remoteIndex, id)
|
_, err := remote.ControlClient.Ping(ctx, &proto.Empty{})
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
remote.MissedPings++
|
||||||
|
log.Printf("Ping %s failed (%d)", remote.Host, remote.MissedPings)
|
||||||
|
if !remote.IsHealthy() {
|
||||||
|
log.Printf("Remote %s unhealthy, removing", remote.Host)
|
||||||
|
p.RemoveHost(remote.Host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
remote.MissedPings = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
func (p *SyncedPool) IsHealthy() bool {
|
||||||
RemoteNegotiate = FrameType(3)
|
p.mu.RLock()
|
||||||
RemoteGrainChanged = FrameType(4)
|
defer p.mu.RUnlock()
|
||||||
AckChange = FrameType(5)
|
for _, r := range p.remoteHosts {
|
||||||
AckError = FrameType(6)
|
if !r.IsHealthy() {
|
||||||
Ping = FrameType(7)
|
return false
|
||||||
Pong = FrameType(8)
|
}
|
||||||
GetCartIds = FrameType(9)
|
}
|
||||||
CartIdsResponse = FrameType(10)
|
return true
|
||||||
RemoteNegotiateResponse = FrameType(11)
|
}
|
||||||
)
|
|
||||||
|
// ------------------------- Negotiation ---------------------------------------
|
||||||
|
|
||||||
func (p *SyncedPool) Negotiate() {
|
func (p *SyncedPool) Negotiate() {
|
||||||
knownHosts := make([]string, 0, len(p.remotes)+1)
|
negotiationCount.Inc()
|
||||||
for _, r := range p.remotes {
|
|
||||||
knownHosts = append(knownHosts, r.Host)
|
|
||||||
}
|
|
||||||
knownHosts = append([]string{p.Hostname}, knownHosts...)
|
|
||||||
|
|
||||||
for _, r := range p.remotes {
|
p.mu.RLock()
|
||||||
hosts, err := r.Negotiate(knownHosts)
|
hosts := make([]string, 0, len(p.remoteHosts)+1)
|
||||||
|
hosts = append(hosts, p.Hostname)
|
||||||
|
for h := range p.remoteHosts {
|
||||||
|
hosts = append(hosts, h)
|
||||||
|
}
|
||||||
|
remotes := make([]*RemoteHostGRPC, 0, len(p.remoteHosts))
|
||||||
|
for _, r := range p.remoteHosts {
|
||||||
|
remotes = append(remotes, r)
|
||||||
|
}
|
||||||
|
p.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, r := range remotes {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
reply, err := r.ControlClient.Negotiate(ctx, &proto.NegotiateRequest{KnownHosts: hosts})
|
||||||
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error negotiating with %s: %v\n", r.Host, err)
|
log.Printf("Negotiate with %s failed: %v", r.Host, err)
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
for _, h := range hosts {
|
for _, h := range reply.Hosts {
|
||||||
if !p.IsKnown(h) {
|
if !p.IsKnown(h) {
|
||||||
p.AddRemote(h)
|
p.AddRemote(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SyncedPool) GetHealthyRemotes() []*RemoteHost {
|
// ------------------------- Grain Management ----------------------------------
|
||||||
// p.mu.RLock()
|
|
||||||
// defer p.mu.RUnlock()
|
// RemoveRemoteGrain removes a remote grain mapping.
|
||||||
remotes := make([]*RemoteHost, 0, len(p.remotes))
|
func (p *SyncedPool) RemoveRemoteGrain(id CartId) {
|
||||||
for _, r := range p.remotes {
|
p.mu.Lock()
|
||||||
|
delete(p.remoteIndex, id)
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpawnRemoteGrain creates/updates a remote grain proxy for a given host.
|
||||||
|
func (p *SyncedPool) SpawnRemoteGrain(id CartId, host string) {
|
||||||
|
if id.String() == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
// If local grain exists, remove it (ownership changed)
|
||||||
|
if g, ok := p.local.grains[id]; ok && g != nil {
|
||||||
|
delete(p.local.grains, id)
|
||||||
|
}
|
||||||
|
remoteHost, ok := p.remoteHosts[host]
|
||||||
|
if !ok {
|
||||||
|
p.mu.Unlock()
|
||||||
|
log.Printf("SpawnRemoteGrain: host %s unknown (id=%s), attempting AddRemote", host, id)
|
||||||
|
go p.AddRemote(host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rg := NewRemoteGrainGRPC(id, host, remoteHost.CartClient)
|
||||||
|
p.remoteIndex[id] = rg
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHealthyRemotes returns a copy slice of healthy remote hosts.
|
||||||
|
func (p *SyncedPool) GetHealthyRemotes() []*RemoteHostGRPC {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
ret := make([]*RemoteHostGRPC, 0, len(p.remoteHosts))
|
||||||
|
for _, r := range p.remoteHosts {
|
||||||
if r.IsHealthy() {
|
if r.IsHealthy() {
|
||||||
remotes = append(remotes, r)
|
ret = append(ret, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return remotes
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestOwnership attempts to become owner of a cart, requiring quorum.
|
||||||
|
// On success local grain is (or will be) created; peers spawn remote proxies.
|
||||||
func (p *SyncedPool) RequestOwnership(id CartId) error {
|
func (p *SyncedPool) RequestOwnership(id CartId) error {
|
||||||
ok := 0
|
ok := 0
|
||||||
all := 0
|
all := 0
|
||||||
|
remotes := p.GetHealthyRemotes()
|
||||||
for _, r := range p.GetHealthyRemotes() {
|
for _, r := range remotes {
|
||||||
if !r.IsHealthy() {
|
ctx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond)
|
||||||
continue
|
reply, err := r.ControlClient.ConfirmOwner(ctx, &proto.OwnerChangeRequest{
|
||||||
}
|
CartId: id.String(),
|
||||||
//log.Printf("Asking for confirmation change of %s to %s (me) with %s\n", id, p.Hostname, r.Host)
|
NewHost: p.Hostname,
|
||||||
err := r.ConfirmChange(id, p.Hostname)
|
})
|
||||||
|
cancel()
|
||||||
all++
|
all++
|
||||||
if err != nil {
|
if err != nil || reply == nil || !reply.Accepted {
|
||||||
if !r.IsHealthy() {
|
log.Printf("ConfirmOwner failure from %s for %s: %v (reply=%v)", r.Host, id, err, reply)
|
||||||
log.Printf("Ownership: Removing host, unable to communicate with %s", r.Host)
|
|
||||||
p.RemoveHost(r)
|
|
||||||
all--
|
|
||||||
} else {
|
|
||||||
log.Printf("Error confirming change: %v from %s\n", err, p.Hostname)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//log.Printf("Remote confirmed change %s\n", r.Host)
|
|
||||||
ok++
|
ok++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quorum rule mirrors legacy:
|
||||||
|
// - If fewer than 3 total, require all.
|
||||||
|
// - Else require majority (ok >= all/2).
|
||||||
if (all < 3 && ok < all) || ok < (all/2) {
|
if (all < 3 && ok < all) || ok < (all/2) {
|
||||||
p.removeLocalGrain(id)
|
p.removeLocalGrain(id)
|
||||||
return fmt.Errorf("quorum not reached")
|
return fmt.Errorf("quorum not reached (ok=%d all=%d)", ok, all)
|
||||||
}
|
}
|
||||||
|
grainSyncCount.Inc()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SyncedPool) removeLocalGrain(id CartId) {
|
func (p *SyncedPool) removeLocalGrain(id CartId) {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
|
||||||
delete(p.local.grains, id)
|
delete(p.local.grains, id)
|
||||||
|
p.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SyncedPool) AddRemote(host string) {
|
// getGrain returns a local or remote grain; if absent, attempts ownership.
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
_, hasHost := p.remotes[host]
|
|
||||||
if host == "" || hasHost || host == p.Hostname {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client := NewConnection(fmt.Sprintf("%s:1338", host))
|
|
||||||
|
|
||||||
var err error
|
|
||||||
pings := 3
|
|
||||||
for pings >= 0 {
|
|
||||||
_, err = client.Call(Ping, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Ping failed when adding %s, trying %d more times\n", host, pings)
|
|
||||||
pings--
|
|
||||||
time.Sleep(time.Millisecond * 300)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
log.Printf("Connected to remote %s", host)
|
|
||||||
|
|
||||||
remote := RemoteHost{
|
|
||||||
Connection: client,
|
|
||||||
MissedPings: 0,
|
|
||||||
Host: host,
|
|
||||||
}
|
|
||||||
|
|
||||||
p.remotes[host] = &remote
|
|
||||||
|
|
||||||
connectedRemotes.Set(float64(len(p.remotes)))
|
|
||||||
|
|
||||||
go p.HandlePing(&remote)
|
|
||||||
go remote.Initialize(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) HandlePing(remote *RemoteHost) {
|
|
||||||
for range time.Tick(time.Second * 3) {
|
|
||||||
|
|
||||||
err := remote.Ping()
|
|
||||||
|
|
||||||
for err != nil {
|
|
||||||
time.Sleep(time.Millisecond * 200)
|
|
||||||
if !remote.IsHealthy() {
|
|
||||||
log.Printf("Removing host, unable to communicate with %s", remote.Host)
|
|
||||||
p.RemoveHost(remote)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = remote.Ping()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) getGrain(id CartId) (Grain, error) {
|
func (p *SyncedPool) getGrain(id CartId) (Grain, error) {
|
||||||
var err error
|
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
defer p.mu.RUnlock()
|
localGrain, isLocal := p.local.grains[id]
|
||||||
localGrain, ok := p.local.grains[id]
|
remoteGrain, isRemote := p.remoteIndex[id]
|
||||||
if !ok {
|
p.mu.RUnlock()
|
||||||
// check if remote grain exists
|
|
||||||
|
|
||||||
remoteGrain, ok := p.remoteIndex[id]
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
remoteLookupCount.Inc()
|
|
||||||
return remoteGrain, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
go p.RequestOwnership(id)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Printf("Error requesting ownership: %v\n", err)
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
localGrain, err = p.local.GetGrain(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if isLocal && localGrain != nil {
|
||||||
|
return localGrain, nil
|
||||||
|
}
|
||||||
|
if isRemote {
|
||||||
|
remoteLookupCount.Inc()
|
||||||
|
return remoteGrain, nil
|
||||||
}
|
}
|
||||||
return localGrain, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SyncedPool) Process(id CartId, messages ...Message) (*FrameWithPayload, error) {
|
// Attempt to claim ownership (async semantics preserved)
|
||||||
pool, err := p.getGrain(id)
|
go p.RequestOwnership(id)
|
||||||
var res *FrameWithPayload
|
|
||||||
|
// Create local grain (lazy spawn) - may be rolled back by quorum failure
|
||||||
|
grain, err := p.local.GetGrain(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return grain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process applies mutation(s) to a grain (local or remote).
|
||||||
|
func (p *SyncedPool) Process(id CartId, messages ...Message) (*FrameWithPayload, error) {
|
||||||
|
grain, err := p.getGrain(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res *FrameWithPayload
|
||||||
for _, m := range messages {
|
for _, m := range messages {
|
||||||
res, err = pool.HandleMessage(&m, false)
|
res, err = grain.HandleMessage(&m, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -463,11 +442,32 @@ func (p *SyncedPool) Process(id CartId, messages ...Message) (*FrameWithPayload,
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns current state of a grain (local or remote).
|
||||||
func (p *SyncedPool) Get(id CartId) (*FrameWithPayload, error) {
|
func (p *SyncedPool) Get(id CartId) (*FrameWithPayload, error) {
|
||||||
grain, err := p.getGrain(id)
|
grain, err := p.getGrain(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return grain.GetCurrentState()
|
return grain.GetCurrentState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close notifies remotes this host is terminating.
|
||||||
|
func (p *SyncedPool) Close() {
|
||||||
|
p.mu.RLock()
|
||||||
|
remotes := make([]*RemoteHostGRPC, 0, len(p.remoteHosts))
|
||||||
|
for _, r := range p.remoteHosts {
|
||||||
|
remotes = append(remotes, r)
|
||||||
|
}
|
||||||
|
p.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, r := range remotes {
|
||||||
|
go func(rh *RemoteHostGRPC) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
_, err := rh.ControlClient.Closing(ctx, &proto.ClosingNotice{Host: p.Hostname})
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Close notify to %s failed: %v", rh.Host, err)
|
||||||
|
}
|
||||||
|
}(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
messages "git.tornberg.me/go-cart-actor/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConnection(t *testing.T) {
|
|
||||||
// TestConnection tests the connection to the server
|
|
||||||
|
|
||||||
localPool := NewGrainLocalPool(100, time.Minute, func(id CartId) (*CartGrain, error) {
|
|
||||||
return &CartGrain{
|
|
||||||
Id: id,
|
|
||||||
storageMessages: []Message{},
|
|
||||||
Items: []*CartItem{},
|
|
||||||
Deliveries: make([]*CartDelivery, 0),
|
|
||||||
TotalPrice: 0,
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
hg, err := NewGrainHandler(localPool, ":1337")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error creating handler: %v\n", err)
|
|
||||||
}
|
|
||||||
if hg == nil {
|
|
||||||
t.Errorf("Expected handler, got nil")
|
|
||||||
}
|
|
||||||
pool, err := NewSyncedPool(localPool, "127.0.0.1", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error creating pool: %v", err)
|
|
||||||
}
|
|
||||||
time.Sleep(400 * time.Millisecond)
|
|
||||||
pool.AddRemote("127.0.0.1")
|
|
||||||
|
|
||||||
go pool.Negotiate()
|
|
||||||
msg := Message{
|
|
||||||
Type: AddItemType,
|
|
||||||
//TimeStamp: time.Now().Unix(),
|
|
||||||
Content: &messages.AddItem{
|
|
||||||
Quantity: 1,
|
|
||||||
Price: 100,
|
|
||||||
Sku: "123",
|
|
||||||
Name: "Test",
|
|
||||||
Image: "Test",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, err := pool.Process(ToCartId("kalle"), msg)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error getting data: %v", err)
|
|
||||||
}
|
|
||||||
if data.StatusCode != 200 {
|
|
||||||
t.Errorf("Expected 200")
|
|
||||||
}
|
|
||||||
log.Println(data)
|
|
||||||
time.Sleep(2 * time.Millisecond)
|
|
||||||
data, err = pool.Get(ToCartId("kalle"))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error getting data: %v", err)
|
|
||||||
}
|
|
||||||
if data == nil {
|
|
||||||
t.Errorf("Expected data, got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Connection struct {
|
|
||||||
address string
|
|
||||||
count uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type FrameType uint32
|
|
||||||
type StatusCode uint32
|
|
||||||
type CheckSum uint32
|
|
||||||
|
|
||||||
type Frame struct {
|
|
||||||
Type FrameType
|
|
||||||
StatusCode StatusCode
|
|
||||||
Length uint32
|
|
||||||
Checksum CheckSum
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Frame) IsValid() bool {
|
|
||||||
return f.Checksum == MakeChecksum(f.Type, f.StatusCode, f.Length)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeChecksum(msg FrameType, statusCode StatusCode, length uint32) CheckSum {
|
|
||||||
sum := CheckSum((uint32(msg) + uint32(statusCode) + length) / 8)
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
type FrameWithPayload struct {
|
|
||||||
Frame
|
|
||||||
Payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeFrameWithPayload(msg FrameType, statusCode StatusCode, payload []byte) FrameWithPayload {
|
|
||||||
len := uint32(len(payload))
|
|
||||||
return FrameWithPayload{
|
|
||||||
Frame: Frame{
|
|
||||||
Type: msg,
|
|
||||||
StatusCode: statusCode,
|
|
||||||
Length: len,
|
|
||||||
Checksum: MakeChecksum(msg, statusCode, len),
|
|
||||||
},
|
|
||||||
Payload: payload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type FrameData interface {
|
|
||||||
ToBytes() []byte
|
|
||||||
FromBytes([]byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnection(address string) *Connection {
|
|
||||||
return &Connection{
|
|
||||||
count: 0,
|
|
||||||
address: address,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendFrame(conn net.Conn, data *FrameWithPayload) error {
|
|
||||||
err := binary.Write(conn, binary.LittleEndian, data.Frame)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = conn.Write(data.Payload)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Connection) CallAsync(msg FrameType, payload []byte, ch chan<- FrameWithPayload) (net.Conn, error) {
|
|
||||||
conn, err := net.Dial("tcp", c.address)
|
|
||||||
if err != nil {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
go WaitForFrame(conn, ch)
|
|
||||||
|
|
||||||
toSend := MakeFrameWithPayload(msg, 1, payload)
|
|
||||||
|
|
||||||
err = SendFrame(conn, &toSend)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error sending frame: %v\n", err)
|
|
||||||
close(ch)
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.count++
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Connection) Call(msg FrameType, data []byte) (*FrameWithPayload, error) {
|
|
||||||
ch := make(chan FrameWithPayload, 1)
|
|
||||||
conn, err := c.CallAsync(msg, data, ch)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
select {
|
|
||||||
case ret := <-ch:
|
|
||||||
return &ret, nil
|
|
||||||
case <-time.After(MaxCallDuration):
|
|
||||||
return nil, fmt.Errorf("timeout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WaitForFrame(conn net.Conn, resultChan chan<- FrameWithPayload) error {
|
|
||||||
var err error
|
|
||||||
var frame Frame
|
|
||||||
//r := bufio.NewReader(conn)
|
|
||||||
|
|
||||||
err = binary.Read(conn, binary.LittleEndian, &frame)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if frame.IsValid() {
|
|
||||||
payload := make([]byte, frame.Length)
|
|
||||||
_, err = conn.Read(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resultChan <- FrameWithPayload{
|
|
||||||
Frame: frame,
|
|
||||||
Payload: payload,
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println("Checksum mismatch")
|
|
||||||
return fmt.Errorf("checksum mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
type GenericListener struct {
|
|
||||||
StopListener bool
|
|
||||||
handlers map[FrameType]func(*FrameWithPayload, chan<- FrameWithPayload) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Connection) Listen() (*GenericListener, error) {
|
|
||||||
l, err := net.Listen("tcp", c.address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret := &GenericListener{
|
|
||||||
handlers: make(map[FrameType]func(*FrameWithPayload, chan<- FrameWithPayload) error),
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
for !ret.StopListener {
|
|
||||||
connection, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error accepting connection: %v\n", err)
|
|
||||||
}
|
|
||||||
go ret.HandleConnection(connection)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
MaxCallDuration = 1500 * time.Millisecond
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *GenericListener) HandleConnection(conn net.Conn) {
|
|
||||||
ch := make(chan FrameWithPayload, 1)
|
|
||||||
go WaitForFrame(conn, ch)
|
|
||||||
select {
|
|
||||||
case frame := <-ch:
|
|
||||||
err := l.HandleFrame(conn, &frame)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error handling frame: %v\n", err)
|
|
||||||
}
|
|
||||||
case <-time.After(MaxCallDuration):
|
|
||||||
close(ch)
|
|
||||||
log.Printf("Timeout waiting for frame\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *GenericListener) AddHandler(msg FrameType, handler func(*FrameWithPayload, chan<- FrameWithPayload) error) {
|
|
||||||
l.handlers[msg] = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *GenericListener) HandleFrame(conn net.Conn, frame *FrameWithPayload) error {
|
|
||||||
handler, ok := l.handlers[frame.Type]
|
|
||||||
if ok {
|
|
||||||
go func() {
|
|
||||||
resultChan := make(chan FrameWithPayload, 1)
|
|
||||||
defer close(resultChan)
|
|
||||||
err := handler(frame, resultChan)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error handling frame: %v\n", err)
|
|
||||||
}
|
|
||||||
result := <-resultChan
|
|
||||||
err = SendFrame(conn, &result)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error sending frame: %v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
conn.Close()
|
|
||||||
return fmt.Errorf("no handler for frame type %d", frame.Type)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,56 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Legacy TCP networking (GenericListener / Frame protocol) has been removed
|
||||||
|
as part of the gRPC migration. This file intentionally contains no tests.
|
||||||
|
|
||||||
|
Keeping an empty Go file (with a package declaration) ensures the old
|
||||||
|
tcp-connection test target no longer runs without causing build issues.
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenericConnection(t *testing.T) {
|
|
||||||
conn := NewConnection("localhost:51337")
|
|
||||||
listener, err := conn.Listen()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error listening: %v\n", err)
|
|
||||||
}
|
|
||||||
datta := []byte("Hello, world!")
|
|
||||||
listener.AddHandler(Ping, func(input *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
resultChan <- MakeFrameWithPayload(Pong, 200, nil)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
listener.AddHandler(1, func(input *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
resultChan <- MakeFrameWithPayload(2, 200, datta)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
listener.AddHandler(3, func(input *FrameWithPayload, resultChan chan<- FrameWithPayload) error {
|
|
||||||
return fmt.Errorf("Error")
|
|
||||||
})
|
|
||||||
r, err := conn.Call(1, datta)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error calling: %v\n", err)
|
|
||||||
}
|
|
||||||
if r.Type != 2 {
|
|
||||||
t.Errorf("Expected type 2, got %d\n", r.Type)
|
|
||||||
}
|
|
||||||
response, err := conn.Call(Ping, nil)
|
|
||||||
if err != nil || response.StatusCode != 200 || response.Type != Pong {
|
|
||||||
t.Errorf("Error connecting to remote %v\n", response)
|
|
||||||
}
|
|
||||||
res, err := conn.Call(3, datta)
|
|
||||||
if res.StatusCode == 200 {
|
|
||||||
t.Errorf("Expected error, got %v\n", res)
|
|
||||||
}
|
|
||||||
|
|
||||||
i := 100
|
|
||||||
results := make(chan FrameWithPayload, i)
|
|
||||||
for i > 0 {
|
|
||||||
go conn.CallAsync(1, datta, results)
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
for i < 100 {
|
|
||||||
r := <-results
|
|
||||||
if r.Type != 2 {
|
|
||||||
t.Errorf("Expected type 2, got %d\n", r.Type)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user