├── .gitignore ├── ch10-DeployLocally ├── test │ ├── policy.csv │ ├── model.conf │ ├── ca-csr.json │ ├── client-csr.json │ ├── server-csr.json │ └── ca-config.json ├── deploy │ └── proglog │ │ ├── values.yaml │ │ ├── .helmignore │ │ ├── templates │ │ ├── service.yaml │ │ └── _helpers.tpl │ │ └── Chart.yaml ├── internal │ ├── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go │ ├── auth │ │ └── authorizer.go │ ├── config │ │ ├── files.go │ │ └── tls.go │ ├── loadbalance │ │ ├── picker.go │ │ └── picker_test.go │ └── discovery │ │ └── membership_test.go ├── Dockerfile ├── cmd │ └── getservers │ │ └── main.go ├── api │ └── v1 │ │ ├── error.go │ │ └── log.proto └── Makefile ├── ch11-DeployCloud ├── test │ ├── policy.csv │ ├── model.conf │ ├── ca-csr.json │ ├── client-csr.json │ ├── server-csr.json │ └── ca-config.json ├── deploy │ ├── proglog │ │ ├── hooks │ │ │ ├── delete-service-per-pod.jsonnet │ │ │ └── create-service-per-pod.jsonnet │ │ ├── values.yaml │ │ ├── .helmignore │ │ ├── templates │ │ │ ├── service.yaml │ │ │ ├── service-per-pod.yaml │ │ │ └── _helpers.tpl │ │ └── Chart.yaml │ └── metacontroller │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ └── templates │ │ ├── metacontroller-rbac.yaml │ │ ├── metacontroller.yaml │ │ └── _helpers.tpl ├── test-getservers-cloud.sh ├── internal │ ├── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go │ ├── auth │ │ └── authorizer.go │ ├── config │ │ ├── files.go │ │ └── tls.go │ ├── loadbalance │ │ ├── picker.go │ │ └── picker_test.go │ └── discovery │ │ └── membership_test.go ├── Dockerfile ├── cmd │ └── getservers │ │ └── main.go ├── api │ └── v1 │ │ ├── error.go │ │ └── log.proto └── Makefile ├── ch5-SecureYourServices ├── test │ ├── policy.csv │ ├── model.conf │ ├── ca-csr.json │ ├── client-csr.json │ ├── server-csr.json │ └── ca-config.json ├── internal │ ├── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go │ ├── auth │ │ └── authorizer.go │ └── config │ │ ├── files.go │ │ └── tls.go ├── api │ └── v1 │ │ ├── log.proto │ │ └── error.go ├── go.mod └── Makefile ├── ch6-ObserveYourServices ├── test │ ├── policy.csv │ ├── model.conf │ ├── ca-csr.json │ ├── client-csr.json │ ├── server-csr.json │ └── ca-config.json ├── internal │ ├── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go │ ├── auth │ │ └── authorizer.go │ └── config │ │ ├── files.go │ │ └── tls.go ├── api │ └── v1 │ │ ├── log.proto │ │ └── error.go ├── go.mod └── Makefile ├── ch8-CoordinateWithConsensus ├── test │ ├── policy.csv │ ├── model.conf │ ├── ca-csr.json │ ├── client-csr.json │ ├── server-csr.json │ └── ca-config.json ├── internal │ ├── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go │ ├── auth │ │ └── authorizer.go │ ├── config │ │ ├── files.go │ │ └── tls.go │ └── discovery │ │ └── membership_test.go ├── api │ └── v1 │ │ ├── error.go │ │ └── log.proto ├── Makefile └── go.mod ├── ch7-ServerSideServiceDiscovery ├── test │ ├── policy.csv │ ├── model.conf │ ├── ca-csr.json │ ├── client-csr.json │ ├── server-csr.json │ └── ca-config.json ├── internal │ ├── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go │ ├── auth │ │ └── authorizer.go │ ├── config │ │ ├── files.go │ │ └── tls.go │ └── discovery │ │ └── membership_test.go ├── api │ └── v1 │ │ ├── log.proto │ │ └── error.go ├── go.mod └── Makefile ├── ch9-ClientSideServiceDiscovery ├── test │ ├── policy.csv │ ├── model.conf │ ├── ca-csr.json │ ├── client-csr.json │ ├── server-csr.json │ └── ca-config.json ├── internal │ ├── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go │ ├── auth │ │ └── authorizer.go │ ├── config │ │ ├── files.go │ │ └── tls.go │ ├── loadbalance │ │ ├── picker.go │ │ └── picker_test.go │ └── discovery │ │ └── membership_test.go ├── api │ └── v1 │ │ ├── error.go │ │ └── log.proto ├── Makefile └── go.mod ├── go.work.sum ├── ch1-LetsGo ├── go.mod ├── go.sum ├── cmd │ └── server │ │ ├── main.go │ │ └── ch1test.sh └── internal │ └── server │ ├── log.go │ └── http.go ├── ch3-WriteALogPacakge ├── Makefile ├── api │ └── v1 │ │ └── log.proto ├── internal │ └── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go └── go.mod ├── ch2-StructureDataWithProtobuf ├── Makefile ├── api │ └── v1 │ │ └── log.proto ├── cmd │ └── server │ │ ├── main.go │ │ └── ch1test.sh ├── go.mod ├── internal │ └── server │ │ └── log.go └── go.sum ├── ch4-ServerRequestWithgRPC ├── internal │ ├── log │ │ ├── config.go │ │ ├── segment_test.go │ │ ├── index_test.go │ │ ├── store.go │ │ └── index.go │ └── server │ │ └── server.go ├── Makefile ├── go.mod └── api │ └── v1 │ ├── log.proto │ └── error.go ├── go.work └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .crt 3 | .key -------------------------------------------------------------------------------- /ch10-DeployLocally/test/policy.csv: -------------------------------------------------------------------------------- 1 | p, root, *, produce 2 | p, root, *, consume 3 | -------------------------------------------------------------------------------- /ch11-DeployCloud/test/policy.csv: -------------------------------------------------------------------------------- 1 | p, root, *, produce 2 | p, root, *, consume 3 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/test/policy.csv: -------------------------------------------------------------------------------- 1 | p, root, *, produce 2 | p, root, *, consume 3 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/test/policy.csv: -------------------------------------------------------------------------------- 1 | p, root, *, produce 2 | p, root, *, consume 3 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/test/policy.csv: -------------------------------------------------------------------------------- 1 | p, root, *, produce 2 | p, root, *, consume 3 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/test/policy.csv: -------------------------------------------------------------------------------- 1 | p, root, *, produce 2 | p, root, *, consume 3 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/test/policy.csv: -------------------------------------------------------------------------------- 1 | p, root, *, produce 2 | p, root, *, consume 3 | -------------------------------------------------------------------------------- /go.work.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 2 | -------------------------------------------------------------------------------- /ch1-LetsGo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require github.com/gorilla/mux v1.8.0 6 | -------------------------------------------------------------------------------- /ch3-WriteALogPacakge/Makefile: -------------------------------------------------------------------------------- 1 | compile: 2 | protoc api/v1/*.proto \ 3 | --go_out=. \ 4 | --go_opt=paths=source_relative \ 5 | --proto_path=. 6 | test: 7 | go test -race ./... -------------------------------------------------------------------------------- /ch1-LetsGo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 3 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/proglog/hooks/delete-service-per-pod.jsonnet: -------------------------------------------------------------------------------- 1 | function(request) { 2 | attachments: [], 3 | finalized: std.length(request.attachments['Service.v1']) == 0 4 | } 5 | -------------------------------------------------------------------------------- /ch2-StructureDataWithProtobuf/Makefile: -------------------------------------------------------------------------------- 1 | compile: 2 | protoc api/v1/*.proto \ 3 | --go_out=. \ 4 | --go_opt=paths=source_relative \ 5 | --proto_path=. 6 | test: 7 | go test -race ./... -------------------------------------------------------------------------------- /ch3-WriteALogPacakge/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | } -------------------------------------------------------------------------------- /ch3-WriteALogPacakge/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Config struct { 4 | Segment struct { 5 | MaxStoreBytes uint64 6 | MaxIndexBytes uint64 7 | InitialOffset uint64 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Config struct { 4 | Segment struct { 5 | MaxStoreBytes uint64 6 | MaxIndexBytes uint64 7 | InitialOffset uint64 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Config struct { 4 | Segment struct { 5 | MaxStoreBytes uint64 6 | MaxIndexBytes uint64 7 | InitialOffset uint64 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Config struct { 4 | Segment struct { 5 | MaxStoreBytes uint64 6 | MaxIndexBytes uint64 7 | InitialOffset uint64 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ch2-StructureDataWithProtobuf/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | } -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Config struct { 4 | Segment struct { 5 | MaxStoreBytes uint64 6 | MaxIndexBytes uint64 7 | InitialOffset uint64 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/proglog/values.yaml: -------------------------------------------------------------------------------- 1 | # proglog 기본값 2 | image: 3 | repository: github.com/nicewook/proglog 4 | tag: 0.0.1 5 | pullPolicy: Always 6 | serfPort: 8401 7 | rpcPort: 8400 8 | replicas: 3 9 | storage: 1Gi 10 | -------------------------------------------------------------------------------- /ch10-DeployLocally/deploy/proglog/values.yaml: -------------------------------------------------------------------------------- 1 | # proglog 기본값 2 | image: 3 | repository: github.com/nicewook/proglog 4 | tag: 0.0.1 5 | pullPolicy: IfNotPresent 6 | serfPort: 8401 7 | rpcPort: 8400 8 | replicas: 3 9 | storage: 1Gi 10 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/Makefile: -------------------------------------------------------------------------------- 1 | compile: 2 | protoc api/v1/*.proto \ 3 | --go_out=. \ 4 | --go-grpc_out=. \ 5 | --go_opt=paths=source_relative \ 6 | --go-grpc_opt=paths=source_relative \ 7 | --proto_path=. 8 | test: 9 | go test -race ./... -------------------------------------------------------------------------------- /ch1-LetsGo/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/nicewook/proglog/internal/server" 7 | ) 8 | 9 | func main() { 10 | srv := server.NewHTTPServer(":8080") 11 | log.Fatal(srv.ListenAndServe()) 12 | } 13 | -------------------------------------------------------------------------------- /ch11-DeployCloud/test-getservers-cloud.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ADDR=$(kubectl get service -l app=service-per-pod -o go-template='{{range .items}}{{(index .status.loadBalancer.ingress 0).ip}}{{"\n"}}{{end}}' | head -n 1) 4 | go run cmd/getservers/main.go -addr=$ADDR:8400 5 | -------------------------------------------------------------------------------- /ch2-StructureDataWithProtobuf/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/nicewook/proglog/internal/server" 7 | ) 8 | 9 | func main() { 10 | srv := server.NewHTTPServer(":8080") 11 | log.Fatal(srv.ListenAndServe()) 12 | } 13 | -------------------------------------------------------------------------------- /ch10-DeployLocally/test/model.conf: -------------------------------------------------------------------------------- 1 | # Request definition 2 | [request_definition] 3 | r = sub, obj, act 4 | 5 | # Policy definition 6 | [policy_definition] 7 | p = sub, obj, act 8 | 9 | # Policy effect 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | # Matchers 14 | [matchers] 15 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act 16 | -------------------------------------------------------------------------------- /ch11-DeployCloud/test/model.conf: -------------------------------------------------------------------------------- 1 | # Request definition 2 | [request_definition] 3 | r = sub, obj, act 4 | 5 | # Policy definition 6 | [policy_definition] 7 | p = sub, obj, act 8 | 9 | # Policy effect 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | # Matchers 14 | [matchers] 15 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act 16 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/test/model.conf: -------------------------------------------------------------------------------- 1 | # Request definition 2 | [request_definition] 3 | r = sub, obj, act 4 | 5 | # Policy definition 6 | [policy_definition] 7 | p = sub, obj, act 8 | 9 | # Policy effect 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | # Matchers 14 | [matchers] 15 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act 16 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/test/model.conf: -------------------------------------------------------------------------------- 1 | # Request definition 2 | [request_definition] 3 | r = sub, obj, act 4 | 5 | # Policy definition 6 | [policy_definition] 7 | p = sub, obj, act 8 | 9 | # Policy effect 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | # Matchers 14 | [matchers] 15 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act 16 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/test/model.conf: -------------------------------------------------------------------------------- 1 | # Request definition 2 | [request_definition] 3 | r = sub, obj, act 4 | 5 | # Policy definition 6 | [policy_definition] 7 | p = sub, obj, act 8 | 9 | # Policy effect 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | # Matchers 14 | [matchers] 15 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act 16 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/test/model.conf: -------------------------------------------------------------------------------- 1 | # Request definition 2 | [request_definition] 3 | r = sub, obj, act 4 | 5 | # Policy definition 6 | [policy_definition] 7 | p = sub, obj, act 8 | 9 | # Policy effect 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | # Matchers 14 | [matchers] 15 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act 16 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/hashicorp/raft" 5 | ) 6 | 7 | type Config struct { 8 | Raft struct { 9 | raft.Config 10 | StreamLayer *StreamLayer 11 | Bootstrap bool 12 | } 13 | Segment struct { 14 | MaxStoreBytes uint64 15 | MaxIndexBytes uint64 16 | InitialOffset uint64 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/test/model.conf: -------------------------------------------------------------------------------- 1 | # Request definition 2 | [request_definition] 3 | r = sub, obj, act 4 | 5 | # Policy definition 6 | [policy_definition] 7 | p = sub, obj, act 8 | 9 | # Policy effect 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | # Matchers 14 | [matchers] 15 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act 16 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/hashicorp/raft" 5 | ) 6 | 7 | type Config struct { 8 | Raft struct { 9 | raft.Config 10 | StreamLayer *StreamLayer 11 | Bootstrap bool 12 | } 13 | Segment struct { 14 | MaxStoreBytes uint64 15 | MaxIndexBytes uint64 16 | InitialOffset uint64 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ch1-LetsGo/cmd/server/ch1test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/zsh -v 2 | curl -X POST localhost:8080 -d '{"record":{"value":"1234"}}' 3 | curl -X POST localhost:8080 -d '{"record":{"value":"5678"}}' 4 | curl -X POST localhost:8080 -d '{"record":{"value":"90AB"}}' 5 | curl -X GET localhost:8080 -d '{"offset":0}' 6 | curl -X GET localhost:8080 -d '{"offset":1}' 7 | curl -X GET localhost:8080 -d '{"offset":2}' 8 | -------------------------------------------------------------------------------- /ch10-DeployLocally/test/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "My Awesome CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "CA", 10 | "L": "ON", 11 | "ST": "Toronto", 12 | "O": "My Awesome Company", 13 | "OU": "CA Services" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ch11-DeployCloud/test/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "My Awesome CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "CA", 10 | "L": "ON", 11 | "ST": "Toronto", 12 | "O": "My Awesome Company", 13 | "OU": "CA Services" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/hashicorp/raft" 5 | ) 6 | 7 | type Config struct { 8 | Raft struct { 9 | raft.Config 10 | BindAddr string 11 | StreamLayer *StreamLayer 12 | Bootstrap bool 13 | } 14 | Segment struct { 15 | MaxStoreBytes uint64 16 | MaxIndexBytes uint64 17 | InitialOffset uint64 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/hashicorp/raft" 5 | ) 6 | 7 | type Config struct { 8 | Raft struct { 9 | raft.Config 10 | BindAddr string 11 | StreamLayer *StreamLayer 12 | Bootstrap bool 13 | } 14 | Segment struct { 15 | MaxStoreBytes uint64 16 | MaxIndexBytes uint64 17 | InitialOffset uint64 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/test/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "My Awesome CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "CA", 10 | "L": "ON", 11 | "ST": "Toronto", 12 | "O": "My Awesome Company", 13 | "OU": "CA Services" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/test/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "My Awesome CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "CA", 10 | "L": "ON", 11 | "ST": "Toronto", 12 | "O": "My Awesome Company", 13 | "OU": "CA Services" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/test/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "My Awesome CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "CA", 10 | "L": "ON", 11 | "ST": "Toronto", 12 | "O": "My Awesome Company", 13 | "OU": "CA Services" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/test/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "My Awesome CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "CA", 10 | "L": "ON", 11 | "ST": "Toronto", 12 | "O": "My Awesome Company", 13 | "OU": "CA Services" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/test/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "My Awesome CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "CA", 10 | "L": "ON", 11 | "ST": "Toronto", 12 | "O": "My Awesome Company", 13 | "OU": "CA Services" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ch11-DeployCloud/test/client-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "client", 3 | "hosts": [""], 4 | "key": { 5 | "algo": "rsa", 6 | "size": 2048 7 | }, 8 | "names": [ 9 | { 10 | "C": "CA", 11 | "L": "ON", 12 | "ST": "Toronto", 13 | "O": "My Company", 14 | "OU": "Distributed Services" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ch2-StructureDataWithProtobuf/cmd/server/ch1test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/zsh -v 2 | curl -X POST localhost:8080 -d '{"record":{"value":"1234"}}' 3 | curl -X POST localhost:8080 -d '{"record":{"value":"5678"}}' 4 | curl -X POST localhost:8080 -d '{"record":{"value":"90AB"}}' 5 | curl -X GET localhost:8080 -d '{"offset":0}' 6 | curl -X GET localhost:8080 -d '{"offset":1}' 7 | curl -X GET localhost:8080 -d '{"offset":2}' 8 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.18 2 | 3 | use ( 4 | // ./ch1-LetsGo 5 | // ./ch2-StructureDataWithProtobuf 6 | // ./ch3-WriteALogPacakge 7 | // ./ch4-ServeRequestsWithgRPC 8 | // ./ch5-SecureYourServices 9 | // ./ch6-ObserveYourServices 10 | // ./ch7-ServerSideServiceDiscovery 11 | // ./ch8-CoordinateWithConsensus 12 | // ./ch9-ClientSideServiceDiscovery 13 | // ./ch10-DeployLocally 14 | // ./ch11-DeployCloud 15 | ) 16 | -------------------------------------------------------------------------------- /ch10-DeployLocally/test/client-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "client", 3 | "hosts": [""], 4 | "key": { 5 | "algo": "rsa", 6 | "size": 2048 7 | }, 8 | "names": [ 9 | { 10 | "C": "CA", 11 | "L": "ON", 12 | "ST": "Toronto", 13 | "O": "My Company", 14 | "OU": "Distributed Services" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/test/client-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "client", 3 | "hosts": [""], 4 | "key": { 5 | "algo": "rsa", 6 | "size": 2048 7 | }, 8 | "names": [ 9 | { 10 | "C": "CA", 11 | "L": "ON", 12 | "ST": "Toronto", 13 | "O": "My Company", 14 | "OU": "Distributed Services" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/test/client-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "client", 3 | "hosts": [""], 4 | "key": { 5 | "algo": "rsa", 6 | "size": 2048 7 | }, 8 | "names": [ 9 | { 10 | "C": "CA", 11 | "L": "ON", 12 | "ST": "Toronto", 13 | "O": "My Company", 14 | "OU": "Distributed Services" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/test/client-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "client", 3 | "hosts": [""], 4 | "key": { 5 | "algo": "rsa", 6 | "size": 2048 7 | }, 8 | "names": [ 9 | { 10 | "C": "CA", 11 | "L": "ON", 12 | "ST": "Toronto", 13 | "O": "My Company", 14 | "OU": "Distributed Services" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/test/client-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "client", 3 | "hosts": [""], 4 | "key": { 5 | "algo": "rsa", 6 | "size": 2048 7 | }, 8 | "names": [ 9 | { 10 | "C": "CA", 11 | "L": "ON", 12 | "ST": "Toronto", 13 | "O": "My Company", 14 | "OU": "Distributed Services" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/test/client-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "client", 3 | "hosts": [""], 4 | "key": { 5 | "algo": "rsa", 6 | "size": 2048 7 | }, 8 | "names": [ 9 | { 10 | "C": "CA", 11 | "L": "ON", 12 | "ST": "Toronto", 13 | "O": "My Company", 14 | "OU": "Distributed Services" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ch11-DeployCloud/test/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "127.0.0.1", 3 | "hosts": [ 4 | "localhost", 5 | "127.0.0.1" 6 | ], 7 | "key": { 8 | "algo": "rsa", 9 | "size": 2048 10 | }, 11 | "names": [ 12 | { 13 | "C": "CA", 14 | "L": "ON", 15 | "ST": "Toronto", 16 | "O": "My Awesome Company", 17 | "OU": "Distributed Services" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ch10-DeployLocally/test/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "127.0.0.1", 3 | "hosts": [ 4 | "localhost", 5 | "127.0.0.1" 6 | ], 7 | "key": { 8 | "algo": "rsa", 9 | "size": 2048 10 | }, 11 | "names": [ 12 | { 13 | "C": "CA", 14 | "L": "ON", 15 | "ST": "Toronto", 16 | "O": "My Awesome Company", 17 | "OU": "Distributed Services" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/test/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "127.0.0.1", 3 | "hosts": [ 4 | "localhost", 5 | "127.0.0.1" 6 | ], 7 | "key": { 8 | "algo": "rsa", 9 | "size": 2048 10 | }, 11 | "names": [ 12 | { 13 | "C": "CA", 14 | "L": "ON", 15 | "ST": "Toronto", 16 | "O": "My Awesome Company", 17 | "OU": "Distributed Services" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/test/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "127.0.0.1", 3 | "hosts": [ 4 | "localhost", 5 | "127.0.0.1" 6 | ], 7 | "key": { 8 | "algo": "rsa", 9 | "size": 2048 10 | }, 11 | "names": [ 12 | { 13 | "C": "CA", 14 | "L": "ON", 15 | "ST": "Toronto", 16 | "O": "My Awesome Company", 17 | "OU": "Distributed Services" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/test/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "127.0.0.1", 3 | "hosts": [ 4 | "localhost", 5 | "127.0.0.1" 6 | ], 7 | "key": { 8 | "algo": "rsa", 9 | "size": 2048 10 | }, 11 | "names": [ 12 | { 13 | "C": "CA", 14 | "L": "ON", 15 | "ST": "Toronto", 16 | "O": "My Awesome Company", 17 | "OU": "Distributed Services" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/test/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "127.0.0.1", 3 | "hosts": [ 4 | "localhost", 5 | "127.0.0.1" 6 | ], 7 | "key": { 8 | "algo": "rsa", 9 | "size": 2048 10 | }, 11 | "names": [ 12 | { 13 | "C": "CA", 14 | "L": "ON", 15 | "ST": "Toronto", 16 | "O": "My Awesome Company", 17 | "OU": "Distributed Services" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/test/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "127.0.0.1", 3 | "hosts": [ 4 | "localhost", 5 | "127.0.0.1" 6 | ], 7 | "key": { 8 | "algo": "rsa", 9 | "size": 2048 10 | }, 11 | "names": [ 12 | { 13 | "C": "CA", 14 | "L": "ON", 15 | "ST": "Toronto", 16 | "O": "My Awesome Company", 17 | "OU": "Distributed Services" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/proglog/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /ch10-DeployLocally/deploy/proglog/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/metacontroller/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /ch3-WriteALogPacakge/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gorilla/mux v1.8.0 7 | github.com/stretchr/testify v1.7.1 8 | github.com/tysonmote/gommap v0.0.2 9 | google.golang.org/protobuf v1.28.0 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.0 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 16 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /ch2-StructureDataWithProtobuf/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require github.com/gorilla/mux v1.8.0 6 | 7 | require ( 8 | github.com/yuin/goldmark v1.4.1 // indirect 9 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect 10 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect 11 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect 12 | golang.org/x/tools v0.1.10 // indirect 13 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /ch10-DeployLocally/test/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "profiles": { 4 | "server": { 5 | "expiry": "8760h", 6 | "usages": [ 7 | "signing", 8 | "key encipherment", 9 | "server auth" 10 | ] 11 | }, 12 | "client": { 13 | "expiry": "8760h", 14 | "usages": [ 15 | "signing", 16 | "key encipherment", 17 | "client auth" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch11-DeployCloud/test/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "profiles": { 4 | "server": { 5 | "expiry": "8760h", 6 | "usages": [ 7 | "signing", 8 | "key encipherment", 9 | "server auth" 10 | ] 11 | }, 12 | "client": { 13 | "expiry": "8760h", 14 | "usages": [ 15 | "signing", 16 | "key encipherment", 17 | "client auth" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/test/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "profiles": { 4 | "server": { 5 | "expiry": "8760h", 6 | "usages": [ 7 | "signing", 8 | "key encipherment", 9 | "server auth" 10 | ] 11 | }, 12 | "client": { 13 | "expiry": "8760h", 14 | "usages": [ 15 | "signing", 16 | "key encipherment", 17 | "client auth" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/test/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "profiles": { 4 | "server": { 5 | "expiry": "8760h", 6 | "usages": [ 7 | "signing", 8 | "key encipherment", 9 | "server auth" 10 | ] 11 | }, 12 | "client": { 13 | "expiry": "8760h", 14 | "usages": [ 15 | "signing", 16 | "key encipherment", 17 | "client auth" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch11-DeployCloud/Dockerfile: -------------------------------------------------------------------------------- 1 | # 스테이지 1 2 | FROM golang:1.18-alpine AS build 3 | WORKDIR /go/src/proglog 4 | COPY . . 5 | RUN CGO_ENABLED=0 go build -o /go/bin/proglog ./cmd/proglog 6 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.1 && \ 7 | wget -qO /go/bin/grpc_health_probe \ 8 | https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 9 | chmod +x /go/bin/grpc_health_probe 10 | 11 | # 스테이지 2 12 | FROM alpine 13 | COPY --from=build /go/bin/proglog /bin/proglog 14 | COPY --from=build /go/bin/grpc_health_probe /bin/grpc_health_probe 15 | ENTRYPOINT ["/bin/proglog"] 16 | 17 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/test/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "profiles": { 4 | "server": { 5 | "expiry": "8760h", 6 | "usages": [ 7 | "signing", 8 | "key encipherment", 9 | "server auth" 10 | ] 11 | }, 12 | "client": { 13 | "expiry": "8760h", 14 | "usages": [ 15 | "signing", 16 | "key encipherment", 17 | "client auth" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch10-DeployLocally/Dockerfile: -------------------------------------------------------------------------------- 1 | # 스테이지 1 2 | FROM golang:1.18-alpine AS build 3 | WORKDIR /go/src/proglog 4 | COPY . . 5 | RUN CGO_ENABLED=0 go build -o /go/bin/proglog ./cmd/proglog 6 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.1 && \ 7 | wget -qO /go/bin/grpc_health_probe \ 8 | https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 9 | chmod +x /go/bin/grpc_health_probe 10 | 11 | # 스테이지 2 12 | FROM alpine 13 | COPY --from=build /go/bin/proglog /bin/proglog 14 | COPY --from=build /go/bin/grpc_health_probe /bin/grpc_health_probe 15 | ENTRYPOINT ["/bin/proglog"] 16 | 17 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/test/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "profiles": { 4 | "server": { 5 | "expiry": "8760h", 6 | "usages": [ 7 | "signing", 8 | "key encipherment", 9 | "server auth" 10 | ] 11 | }, 12 | "client": { 13 | "expiry": "8760h", 14 | "usages": [ 15 | "signing", 16 | "key encipherment", 17 | "client auth" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/test/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "profiles": { 4 | "server": { 5 | "expiry": "8760h", 6 | "usages": [ 7 | "signing", 8 | "key encipherment", 9 | "server auth" 10 | ] 11 | }, 12 | "client": { 13 | "expiry": "8760h", 14 | "usages": [ 15 | "signing", 16 | "key encipherment", 17 | "client auth" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch10-DeployLocally/cmd/getservers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | 9 | api "github.com/nicewook/proglog/api/v1" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | func main() { 14 | addr := flag.String("addr", ":8400", "service address") 15 | flag.Parse() 16 | conn, err := grpc.Dial(*addr, grpc.WithInsecure()) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | client := api.NewLogClient(conn) 21 | ctx := context.Background() 22 | res, err := client.GetServers(ctx, &api.GetServersRequest{}) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | fmt.Println("servers:") 27 | for _, server := range res.Servers { 28 | fmt.Printf("\t- %v\n", server) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ch11-DeployCloud/cmd/getservers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | 9 | api "github.com/nicewook/proglog/api/v1" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | func main() { 14 | addr := flag.String("addr", ":8400", "service address") 15 | flag.Parse() 16 | conn, err := grpc.Dial(*addr, grpc.WithInsecure()) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | client := api.NewLogClient(conn) 21 | ctx := context.Background() 22 | res, err := client.GetServers(ctx, &api.GetServersRequest{}) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | fmt.Println("servers:") 27 | for _, server := range res.Servers { 28 | fmt.Printf("\t- %v\n", server) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.7.1 7 | github.com/tysonmote/gommap v0.0.2 8 | google.golang.org/genproto v0.0.0-20220623142657-077d458a5694 9 | google.golang.org/grpc v1.47.0 10 | google.golang.org/protobuf v1.28.0 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.0 // indirect 15 | github.com/golang/protobuf v1.5.2 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect 18 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect 19 | golang.org/x/text v0.3.5 // indirect 20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /ch10-DeployLocally/deploy/proglog/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "proglog.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: {{ include "proglog.labels" . | nindent 4 }} 7 | spec: 8 | clusterIP: None 9 | publishNotReadyAddresses: true 10 | ports: 11 | - name: rpc 12 | port: {{ .Values.rpcPort }} 13 | targetPort: {{ .Values.rpcPort }} 14 | - name: serf-tcp 15 | protocol: "TCP" 16 | port: {{ .Values.serfPort }} 17 | targetPort: {{ .Values.serfPort }} 18 | - name: serf-udp 19 | protocol: "UDP" 20 | port: {{ .Values.serfPort }} 21 | targetPort: {{ .Values.serfPort }} 22 | selector: {{ include "proglog.selectorLabels" . | nindent 4 }} 23 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/proglog/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "proglog.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: {{ include "proglog.labels" . | nindent 4 }} 7 | spec: 8 | clusterIP: None 9 | publishNotReadyAddresses: true 10 | ports: 11 | - name: rpc 12 | port: {{ .Values.rpcPort }} 13 | targetPort: {{ .Values.rpcPort }} 14 | - name: serf-tcp 15 | protocol: "TCP" 16 | port: {{ .Values.serfPort }} 17 | targetPort: {{ .Values.serfPort }} 18 | - name: serf-udp 19 | protocol: "UDP" 20 | port: {{ .Values.serfPort }} 21 | targetPort: {{ .Values.serfPort }} 22 | selector: {{ include "proglog.selectorLabels" . | nindent 4 }} 23 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | } 8 | 9 | message ProduceRequest { 10 | Record record = 1; 11 | } 12 | 13 | message ProduceResponse { 14 | uint64 offset = 1; 15 | } 16 | 17 | message ConsumeRequest { 18 | uint64 offset = 1; 19 | } 20 | 21 | message ConsumeResponse { 22 | Record record = 1; 23 | } 24 | 25 | service Log { 26 | rpc Produce(ProduceRequest) returns (ProduceResponse) {} 27 | rpc Consume(ConsumeRequest) returns (ConsumeResponse) {} 28 | rpc ConsumeStream(ConsumeRequest) returns (stream ConsumeResponse) {} 29 | rpc ProduceStream(stream ProduceRequest) returns (stream ProduceResponse) {} 30 | } 31 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | } 8 | 9 | message ProduceRequest { 10 | Record record = 1; 11 | } 12 | 13 | message ProduceResponse { 14 | uint64 offset = 1; 15 | } 16 | 17 | message ConsumeRequest { 18 | uint64 offset = 1; 19 | } 20 | 21 | message ConsumeResponse { 22 | Record record = 1; 23 | } 24 | 25 | service Log { 26 | rpc Produce(ProduceRequest) returns (ProduceResponse) {} 27 | rpc Consume(ConsumeRequest) returns (ConsumeResponse) {} 28 | rpc ConsumeStream(ConsumeRequest) returns (stream ConsumeResponse) {} 29 | rpc ProduceStream(stream ProduceRequest) returns (stream ProduceResponse) {} 30 | } 31 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | } 8 | 9 | message ProduceRequest { 10 | Record record = 1; 11 | } 12 | 13 | message ProduceResponse { 14 | uint64 offset = 1; 15 | } 16 | 17 | message ConsumeRequest { 18 | uint64 offset = 1; 19 | } 20 | 21 | message ConsumeResponse { 22 | Record record = 1; 23 | } 24 | 25 | service Log { 26 | rpc Produce(ProduceRequest) returns (ProduceResponse) {} 27 | rpc Consume(ConsumeRequest) returns (ConsumeResponse) {} 28 | rpc ConsumeStream(ConsumeRequest) returns (stream ConsumeResponse) {} 29 | rpc ProduceStream(stream ProduceRequest) returns (stream ProduceResponse) {} 30 | } 31 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/auth/authorizer.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/casbin/casbin" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func New(model, policy string) *Authorizer { 12 | enforcer := casbin.NewEnforcer(model, policy) 13 | return &Authorizer{ 14 | enforcer: enforcer, 15 | } 16 | } 17 | 18 | type Authorizer struct { 19 | enforcer *casbin.Enforcer 20 | } 21 | 22 | func (a *Authorizer) Authorize(subject, object, action string) error { 23 | if !a.enforcer.Enforce(subject, object, action) { 24 | msg := fmt.Sprintf( 25 | "%s not permitted to %s to %s", 26 | subject, 27 | action, 28 | object, 29 | ) 30 | st := status.New(codes.PermissionDenied, msg) 31 | return st.Err() 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | } 8 | 9 | message ProduceRequest { 10 | Record record = 1; 11 | } 12 | 13 | message ProduceResponse { 14 | uint64 offset = 1; 15 | } 16 | 17 | message ConsumeRequest { 18 | uint64 offset = 1; 19 | } 20 | 21 | message ConsumeResponse { 22 | Record record = 1; 23 | } 24 | 25 | service Log { 26 | rpc Produce(ProduceRequest) returns (ProduceResponse) {} 27 | rpc Consume(ConsumeRequest) returns (ConsumeResponse) {} 28 | rpc ConsumeStream(ConsumeRequest) returns (stream ConsumeResponse) {} 29 | rpc ProduceStream(stream ProduceRequest) returns (stream ProduceResponse) {} 30 | } 31 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/auth/authorizer.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/casbin/casbin" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func New(model, policy string) *Authorizer { 12 | enforcer := casbin.NewEnforcer(model, policy) 13 | return &Authorizer{ 14 | enforcer: enforcer, 15 | } 16 | } 17 | 18 | type Authorizer struct { 19 | enforcer *casbin.Enforcer 20 | } 21 | 22 | func (a *Authorizer) Authorize(subject, object, action string) error { 23 | if !a.enforcer.Enforce(subject, object, action) { 24 | msg := fmt.Sprintf( 25 | "%s not permitted to %s to %s", 26 | subject, 27 | action, 28 | object, 29 | ) 30 | st := status.New(codes.PermissionDenied, msg) 31 | return st.Err() 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/internal/auth/authorizer.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/casbin/casbin" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func New(model, policy string) *Authorizer { 12 | enforcer := casbin.NewEnforcer(model, policy) 13 | return &Authorizer{ 14 | enforcer: enforcer, 15 | } 16 | } 17 | 18 | type Authorizer struct { 19 | enforcer *casbin.Enforcer 20 | } 21 | 22 | func (a *Authorizer) Authorize(subject, object, action string) error { 23 | if !a.enforcer.Enforce(subject, object, action) { 24 | msg := fmt.Sprintf( 25 | "%s not permitted to %s to %s", 26 | subject, 27 | action, 28 | object, 29 | ) 30 | st := status.New(codes.PermissionDenied, msg) 31 | return st.Err() 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/internal/auth/authorizer.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/casbin/casbin" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func New(model, policy string) *Authorizer { 12 | enforcer := casbin.NewEnforcer(model, policy) 13 | return &Authorizer{ 14 | enforcer: enforcer, 15 | } 16 | } 17 | 18 | type Authorizer struct { 19 | enforcer *casbin.Enforcer 20 | } 21 | 22 | func (a *Authorizer) Authorize(subject, object, action string) error { 23 | if !a.enforcer.Enforce(subject, object, action) { 24 | msg := fmt.Sprintf( 25 | "%s not permitted to %s to %s", 26 | subject, 27 | action, 28 | object, 29 | ) 30 | st := status.New(codes.PermissionDenied, msg) 31 | return st.Err() 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/auth/authorizer.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/casbin/casbin" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func New(model, policy string) *Authorizer { 12 | enforcer := casbin.NewEnforcer(model, policy) 13 | return &Authorizer{ 14 | enforcer: enforcer, 15 | } 16 | } 17 | 18 | type Authorizer struct { 19 | enforcer *casbin.Enforcer 20 | } 21 | 22 | func (a *Authorizer) Authorize(subject, object, action string) error { 23 | if !a.enforcer.Enforce(subject, object, action) { 24 | msg := fmt.Sprintf( 25 | "%s not permitted to %s to %s", 26 | subject, 27 | action, 28 | object, 29 | ) 30 | st := status.New(codes.PermissionDenied, msg) 31 | return st.Err() 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/auth/authorizer.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/casbin/casbin" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func New(model, policy string) *Authorizer { 12 | enforcer := casbin.NewEnforcer(model, policy) 13 | return &Authorizer{ 14 | enforcer: enforcer, 15 | } 16 | } 17 | 18 | type Authorizer struct { 19 | enforcer *casbin.Enforcer 20 | } 21 | 22 | func (a *Authorizer) Authorize(subject, object, action string) error { 23 | if !a.enforcer.Enforce(subject, object, action) { 24 | msg := fmt.Sprintf( 25 | "%s not permitted to %s to %s", 26 | subject, 27 | action, 28 | object, 29 | ) 30 | st := status.New(codes.PermissionDenied, msg) 31 | return st.Err() 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/auth/authorizer.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/casbin/casbin" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | func New(model, policy string) *Authorizer { 12 | enforcer := casbin.NewEnforcer(model, policy) 13 | return &Authorizer{ 14 | enforcer: enforcer, 15 | } 16 | } 17 | 18 | type Authorizer struct { 19 | enforcer *casbin.Enforcer 20 | } 21 | 22 | func (a *Authorizer) Authorize(subject, object, action string) error { 23 | if !a.enforcer.Enforce(subject, object, action) { 24 | msg := fmt.Sprintf( 25 | "%s not permitted to %s to %s", 26 | subject, 27 | action, 28 | object, 29 | ) 30 | st := status.New(codes.PermissionDenied, msg) 31 | return st.Err() 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /ch10-DeployLocally/api/v1/error.go: -------------------------------------------------------------------------------- 1 | package log_v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/genproto/googleapis/rpc/errdetails" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | type ErrOffsetOutOfRange struct { 11 | Offset uint64 12 | } 13 | 14 | func (e ErrOffsetOutOfRange) GRPCStatus() *status.Status { 15 | st := status.New( 16 | 404, 17 | fmt.Sprintf("offset out of range: %d", e.Offset), 18 | ) 19 | msg := fmt.Sprintf( 20 | "The requested offset is outside the log's range: %d", 21 | e.Offset, 22 | ) 23 | d := &errdetails.LocalizedMessage{ 24 | Locale: "en-US", 25 | Message: msg, 26 | } 27 | std, err := st.WithDetails(d) 28 | if err != nil { 29 | return st 30 | } 31 | return std 32 | } 33 | 34 | func (e ErrOffsetOutOfRange) Error() string { 35 | return e.GRPCStatus().Err().Error() 36 | } 37 | -------------------------------------------------------------------------------- /ch11-DeployCloud/api/v1/error.go: -------------------------------------------------------------------------------- 1 | package log_v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/genproto/googleapis/rpc/errdetails" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | type ErrOffsetOutOfRange struct { 11 | Offset uint64 12 | } 13 | 14 | func (e ErrOffsetOutOfRange) GRPCStatus() *status.Status { 15 | st := status.New( 16 | 404, 17 | fmt.Sprintf("offset out of range: %d", e.Offset), 18 | ) 19 | msg := fmt.Sprintf( 20 | "The requested offset is outside the log's range: %d", 21 | e.Offset, 22 | ) 23 | d := &errdetails.LocalizedMessage{ 24 | Locale: "en-US", 25 | Message: msg, 26 | } 27 | std, err := st.WithDetails(d) 28 | if err != nil { 29 | return st 30 | } 31 | return std 32 | } 33 | 34 | func (e ErrOffsetOutOfRange) Error() string { 35 | return e.GRPCStatus().Err().Error() 36 | } 37 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/api/v1/error.go: -------------------------------------------------------------------------------- 1 | package log_v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/genproto/googleapis/rpc/errdetails" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | type ErrOffsetOutOfRange struct { 11 | Offset uint64 12 | } 13 | 14 | func (e ErrOffsetOutOfRange) GRPCStatus() *status.Status { 15 | st := status.New( 16 | 404, 17 | fmt.Sprintf("offset out of range: %d", e.Offset), 18 | ) 19 | msg := fmt.Sprintf( 20 | "The requested offset is outside the log's range: %d", 21 | e.Offset, 22 | ) 23 | d := &errdetails.LocalizedMessage{ 24 | Locale: "en-US", 25 | Message: msg, 26 | } 27 | std, err := st.WithDetails(d) 28 | if err != nil { 29 | return st 30 | } 31 | return std 32 | } 33 | 34 | func (e ErrOffsetOutOfRange) Error() string { 35 | return e.GRPCStatus().Err().Error() 36 | } 37 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/api/v1/error.go: -------------------------------------------------------------------------------- 1 | package log_v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/genproto/googleapis/rpc/errdetails" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | type ErrOffsetOutOfRange struct { 11 | Offset uint64 12 | } 13 | 14 | func (e ErrOffsetOutOfRange) GRPCStatus() *status.Status { 15 | st := status.New( 16 | 404, 17 | fmt.Sprintf("offset out of range: %d", e.Offset), 18 | ) 19 | msg := fmt.Sprintf( 20 | "The requested offset is outside the log's range: %d", 21 | e.Offset, 22 | ) 23 | d := &errdetails.LocalizedMessage{ 24 | Locale: "en-US", 25 | Message: msg, 26 | } 27 | std, err := st.WithDetails(d) 28 | if err != nil { 29 | return st 30 | } 31 | return std 32 | } 33 | 34 | func (e ErrOffsetOutOfRange) Error() string { 35 | return e.GRPCStatus().Err().Error() 36 | } 37 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/api/v1/error.go: -------------------------------------------------------------------------------- 1 | package log_v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/genproto/googleapis/rpc/errdetails" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | type ErrOffsetOutOfRange struct { 11 | Offset uint64 12 | } 13 | 14 | func (e ErrOffsetOutOfRange) GRPCStatus() *status.Status { 15 | st := status.New( 16 | 404, 17 | fmt.Sprintf("offset out of range: %d", e.Offset), 18 | ) 19 | msg := fmt.Sprintf( 20 | "The requested offset is outside the log's range: %d", 21 | e.Offset, 22 | ) 23 | d := &errdetails.LocalizedMessage{ 24 | Locale: "en-US", 25 | Message: msg, 26 | } 27 | std, err := st.WithDetails(d) 28 | if err != nil { 29 | return st 30 | } 31 | return std 32 | } 33 | 34 | func (e ErrOffsetOutOfRange) Error() string { 35 | return e.GRPCStatus().Err().Error() 36 | } 37 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/api/v1/error.go: -------------------------------------------------------------------------------- 1 | package log_v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/genproto/googleapis/rpc/errdetails" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | type ErrOffsetOutOfRange struct { 11 | Offset uint64 12 | } 13 | 14 | func (e ErrOffsetOutOfRange) GRPCStatus() *status.Status { 15 | st := status.New( 16 | 404, 17 | fmt.Sprintf("offset out of range: %d", e.Offset), 18 | ) 19 | msg := fmt.Sprintf( 20 | "The requested offset is outside the log's range: %d", 21 | e.Offset, 22 | ) 23 | d := &errdetails.LocalizedMessage{ 24 | Locale: "en-US", 25 | Message: msg, 26 | } 27 | std, err := st.WithDetails(d) 28 | if err != nil { 29 | return st 30 | } 31 | return std 32 | } 33 | 34 | func (e ErrOffsetOutOfRange) Error() string { 35 | return e.GRPCStatus().Err().Error() 36 | } 37 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/api/v1/error.go: -------------------------------------------------------------------------------- 1 | package log_v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/genproto/googleapis/rpc/errdetails" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | type ErrOffsetOutOfRange struct { 11 | Offset uint64 12 | } 13 | 14 | func (e ErrOffsetOutOfRange) GRPCStatus() *status.Status { 15 | st := status.New( 16 | 404, 17 | fmt.Sprintf("offset out of range: %d", e.Offset), 18 | ) 19 | msg := fmt.Sprintf( 20 | "The requested offset is outside the log's range: %d", 21 | e.Offset, 22 | ) 23 | d := &errdetails.LocalizedMessage{ 24 | Locale: "en-US", 25 | Message: msg, 26 | } 27 | std, err := st.WithDetails(d) 28 | if err != nil { 29 | return st 30 | } 31 | return std 32 | } 33 | 34 | func (e ErrOffsetOutOfRange) Error() string { 35 | return e.GRPCStatus().Err().Error() 36 | } 37 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/api/v1/error.go: -------------------------------------------------------------------------------- 1 | package log_v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/genproto/googleapis/rpc/errdetails" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | type ErrOffsetOutOfRange struct { 11 | Offset uint64 12 | } 13 | 14 | func (e ErrOffsetOutOfRange) GRPCStatus() *status.Status { 15 | st := status.New( 16 | 404, 17 | fmt.Sprintf("offset out of range: %d", e.Offset), 18 | ) 19 | msg := fmt.Sprintf( 20 | "The requested offset is outside the log's range: %d", 21 | e.Offset, 22 | ) 23 | d := &errdetails.LocalizedMessage{ 24 | Locale: "en-US", 25 | Message: msg, 26 | } 27 | std, err := st.WithDetails(d) 28 | if err != nil { 29 | return st 30 | } 31 | return std 32 | } 33 | 34 | func (e ErrOffsetOutOfRange) Error() string { 35 | return e.GRPCStatus().Err().Error() 36 | } 37 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | uint64 term = 3; 8 | uint32 type = 4; 9 | } 10 | 11 | message ProduceRequest { 12 | Record record = 1; 13 | } 14 | 15 | message ProduceResponse { 16 | uint64 offset = 1; 17 | } 18 | 19 | message ConsumeRequest { 20 | uint64 offset = 1; 21 | } 22 | 23 | message ConsumeResponse { 24 | Record record = 1; 25 | } 26 | 27 | service Log { 28 | rpc Produce(ProduceRequest) returns (ProduceResponse) {} 29 | rpc Consume(ConsumeRequest) returns (ConsumeResponse) {} 30 | rpc ConsumeStream(ConsumeRequest) returns (stream ConsumeResponse) {} 31 | rpc ProduceStream(stream ProduceRequest) returns (stream ProduceResponse) {} 32 | } 33 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/casbin/casbin v1.9.1 7 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 8 | github.com/stretchr/testify v1.7.1 9 | github.com/tysonmote/gommap v0.0.2 10 | google.golang.org/genproto v0.0.0-20220623142657-077d458a5694 11 | google.golang.org/grpc v1.47.0 12 | google.golang.org/protobuf v1.28.0 13 | ) 14 | 15 | require ( 16 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/golang/protobuf v1.5.2 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect 21 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect 22 | golang.org/x/text v0.3.5 // indirect 23 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/casbin/casbin v1.9.1 7 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 8 | github.com/stretchr/testify v1.7.1 9 | github.com/tysonmote/gommap v0.0.2 10 | google.golang.org/genproto v0.0.0-20220623142657-077d458a5694 11 | google.golang.org/grpc v1.47.0 12 | google.golang.org/protobuf v1.28.0 13 | ) 14 | 15 | require ( 16 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/golang/protobuf v1.5.2 // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | go.opencensus.io v0.23.0 // indirect 21 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect 22 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect 23 | golang.org/x/text v0.3.5 // indirect 24 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/proglog/hooks/create-service-per-pod.jsonnet: -------------------------------------------------------------------------------- 1 | function(request) { 2 | local statefulset = request.object, 3 | local labelKey = statefulset.metadata.annotations["service-per-pod-label"], 4 | local ports = statefulset.metadata.annotations["service-per-pod-ports"], 5 | 6 | attachments: [ 7 | { 8 | apiVersion: "v1", 9 | kind: "Service", 10 | metadata: { 11 | name: statefulset.metadata.name + "-" + index, 12 | labels: {app: "service-per-pod"} 13 | }, 14 | spec: { 15 | type: "LoadBalancer", 16 | selector: { 17 | [labelKey]: statefulset.metadata.name + "-" + index 18 | }, 19 | ports: [ 20 | { 21 | local parts = std.split(portnums, ":"), 22 | port: std.parseInt(parts[0]), 23 | targetPort: std.parseInt(parts[1]), 24 | } 25 | for portnums in std.split(ports, ",") 26 | ] 27 | } 28 | } 29 | for index in std.range(0, statefulset.spec.replicas - 1) 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/config/files.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | CAFile = configFile("ca.pem") 10 | ServerCertFile = configFile("server.pem") 11 | ServerKeyFile = configFile("server-key.pem") 12 | ClientCertFile = configFile("client.pem") 13 | ClientKeyFile = configFile("client-key.pem") 14 | RootClientCertFile = configFile("root-client.pem") 15 | RootClientKeyFile = configFile("root-client-key.pem") 16 | NobodyClientCertFile = configFile("nobody-client.pem") 17 | NobodyClientKeyFile = configFile("nobody-client-key.pem") 18 | ACLModelFile = configFile("model.conf") 19 | ACLPolicyFile = configFile("policy.csv") 20 | ) 21 | 22 | 23 | func configFile(filename string) string { 24 | if dir := os.Getenv("CONFIG_DIR"); dir != "" { 25 | return filepath.Join(dir, filename) 26 | } 27 | homeDir, err := os.UserHomeDir() 28 | if err != nil { 29 | panic(err) 30 | } 31 | return filepath.Join(homeDir, ".proglog", filename) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ch1-LetsGo/internal/server/log.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | // fmt.Errorf 보다는 이게 낫지 9 | var ErrOffsetNotFound = errors.New("offset not found") 10 | 11 | // 사용하는 놈인 Log 위에 정의 12 | // Offset 값은 의미가 있나? 13 | type Record struct { 14 | Value []byte `json:"value"` 15 | Offset uint64 `json:"offset"` 16 | } 17 | 18 | type Log struct { 19 | mu sync.Mutex 20 | records []Record 21 | } 22 | 23 | func NewLog() *Log { 24 | return &Log{} 25 | } 26 | 27 | // 길이는 int64가 낫지 않나? 28 | // 왜 l이 아니고 c일까? 29 | // mutex는 RWMutex가 낫지 않으려나? 30 | func (c *Log) Append(record Record) (uint64, error) { 31 | c.mu.Lock() 32 | defer c.mu.Unlock() 33 | record.Offset = uint64(len(c.records)) // 추가할 위치 인덱스 == 오프셋 34 | c.records = append(c.records, record) 35 | return record.Offset, nil 36 | } 37 | 38 | // 39 | func (c *Log) Read(offset uint64) (Record, error) { 40 | c.mu.Lock() 41 | defer c.mu.Unlock() 42 | if offset >= uint64(len(c.records)) { 43 | return Record{}, ErrOffsetNotFound 44 | } 45 | return c.records[offset], nil 46 | } 47 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/config/files.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | CAFile = configFile("ca.pem") 10 | ServerCertFile = configFile("server.pem") 11 | ServerKeyFile = configFile("server-key.pem") 12 | ClientCertFile = configFile("client.pem") 13 | ClientKeyFile = configFile("client-key.pem") 14 | RootClientCertFile = configFile("root-client.pem") 15 | RootClientKeyFile = configFile("root-client-key.pem") 16 | NobodyClientCertFile = configFile("nobody-client.pem") 17 | NobodyClientKeyFile = configFile("nobody-client-key.pem") 18 | ACLModelFile = configFile("model.conf") 19 | ACLPolicyFile = configFile("policy.csv") 20 | ) 21 | 22 | 23 | func configFile(filename string) string { 24 | if dir := os.Getenv("CONFIG_DIR"); dir != "" { 25 | return filepath.Join(dir, filename) 26 | } 27 | homeDir, err := os.UserHomeDir() 28 | if err != nil { 29 | panic(err) 30 | } 31 | return filepath.Join(homeDir, ".proglog", filename) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/internal/config/files.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | CAFile = configFile("ca.pem") 10 | ServerCertFile = configFile("server.pem") 11 | ServerKeyFile = configFile("server-key.pem") 12 | ClientCertFile = configFile("client.pem") 13 | ClientKeyFile = configFile("client-key.pem") 14 | RootClientCertFile = configFile("root-client.pem") 15 | RootClientKeyFile = configFile("root-client-key.pem") 16 | NobodyClientCertFile = configFile("nobody-client.pem") 17 | NobodyClientKeyFile = configFile("nobody-client-key.pem") 18 | ACLModelFile = configFile("model.conf") 19 | ACLPolicyFile = configFile("policy.csv") 20 | ) 21 | 22 | 23 | func configFile(filename string) string { 24 | if dir := os.Getenv("CONFIG_DIR"); dir != "" { 25 | return filepath.Join(dir, filename) 26 | } 27 | homeDir, err := os.UserHomeDir() 28 | if err != nil { 29 | panic(err) 30 | } 31 | return filepath.Join(homeDir, ".proglog", filename) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/internal/config/files.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | CAFile = configFile("ca.pem") 10 | ServerCertFile = configFile("server.pem") 11 | ServerKeyFile = configFile("server-key.pem") 12 | ClientCertFile = configFile("client.pem") 13 | ClientKeyFile = configFile("client-key.pem") 14 | RootClientCertFile = configFile("root-client.pem") 15 | RootClientKeyFile = configFile("root-client-key.pem") 16 | NobodyClientCertFile = configFile("nobody-client.pem") 17 | NobodyClientKeyFile = configFile("nobody-client-key.pem") 18 | ACLModelFile = configFile("model.conf") 19 | ACLPolicyFile = configFile("policy.csv") 20 | ) 21 | 22 | 23 | func configFile(filename string) string { 24 | if dir := os.Getenv("CONFIG_DIR"); dir != "" { 25 | return filepath.Join(dir, filename) 26 | } 27 | homeDir, err := os.UserHomeDir() 28 | if err != nil { 29 | panic(err) 30 | } 31 | return filepath.Join(homeDir, ".proglog", filename) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/config/files.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | CAFile = configFile("ca.pem") 10 | ServerCertFile = configFile("server.pem") 11 | ServerKeyFile = configFile("server-key.pem") 12 | ClientCertFile = configFile("client.pem") 13 | ClientKeyFile = configFile("client-key.pem") 14 | RootClientCertFile = configFile("root-client.pem") 15 | RootClientKeyFile = configFile("root-client-key.pem") 16 | NobodyClientCertFile = configFile("nobody-client.pem") 17 | NobodyClientKeyFile = configFile("nobody-client-key.pem") 18 | ACLModelFile = configFile("model.conf") 19 | ACLPolicyFile = configFile("policy.csv") 20 | ) 21 | 22 | 23 | func configFile(filename string) string { 24 | if dir := os.Getenv("CONFIG_DIR"); dir != "" { 25 | return filepath.Join(dir, filename) 26 | } 27 | homeDir, err := os.UserHomeDir() 28 | if err != nil { 29 | panic(err) 30 | } 31 | return filepath.Join(homeDir, ".proglog", filename) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/config/files.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | CAFile = configFile("ca.pem") 10 | ServerCertFile = configFile("server.pem") 11 | ServerKeyFile = configFile("server-key.pem") 12 | ClientCertFile = configFile("client.pem") 13 | ClientKeyFile = configFile("client-key.pem") 14 | RootClientCertFile = configFile("root-client.pem") 15 | RootClientKeyFile = configFile("root-client-key.pem") 16 | NobodyClientCertFile = configFile("nobody-client.pem") 17 | NobodyClientKeyFile = configFile("nobody-client-key.pem") 18 | ACLModelFile = configFile("model.conf") 19 | ACLPolicyFile = configFile("policy.csv") 20 | ) 21 | 22 | 23 | func configFile(filename string) string { 24 | if dir := os.Getenv("CONFIG_DIR"); dir != "" { 25 | return filepath.Join(dir, filename) 26 | } 27 | homeDir, err := os.UserHomeDir() 28 | if err != nil { 29 | panic(err) 30 | } 31 | return filepath.Join(homeDir, ".proglog", filename) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/config/files.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | CAFile = configFile("ca.pem") 10 | ServerCertFile = configFile("server.pem") 11 | ServerKeyFile = configFile("server-key.pem") 12 | ClientCertFile = configFile("client.pem") 13 | ClientKeyFile = configFile("client-key.pem") 14 | RootClientCertFile = configFile("root-client.pem") 15 | RootClientKeyFile = configFile("root-client-key.pem") 16 | NobodyClientCertFile = configFile("nobody-client.pem") 17 | NobodyClientKeyFile = configFile("nobody-client-key.pem") 18 | ACLModelFile = configFile("model.conf") 19 | ACLPolicyFile = configFile("policy.csv") 20 | ) 21 | 22 | 23 | func configFile(filename string) string { 24 | if dir := os.Getenv("CONFIG_DIR"); dir != "" { 25 | return filepath.Join(dir, filename) 26 | } 27 | homeDir, err := os.UserHomeDir() 28 | if err != nil { 29 | panic(err) 30 | } 31 | return filepath.Join(homeDir, ".proglog", filename) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ch2-StructureDataWithProtobuf/internal/server/log.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | // fmt.Errorf 보다는 이게 낫지 9 | var ErrOffsetNotFound = errors.New("offset not found") 10 | 11 | // 사용하는 놈인 Log 위에 정의 12 | // Offset 값은 의미가 있나? 13 | type Record struct { 14 | Value []byte `json:"value"` 15 | Offset uint64 `json:"offset"` 16 | } 17 | 18 | type Log struct { 19 | mu sync.Mutex 20 | records []Record 21 | } 22 | 23 | func NewLog() *Log { 24 | return &Log{} 25 | } 26 | 27 | // 길이는 int64가 낫지 않나? 28 | // 왜 l이 아니고 c일까? 29 | // mutex는 RWMutex가 낫지 않으려나? 30 | func (c *Log) Append(record Record) (uint64, error) { 31 | c.mu.Lock() 32 | defer c.mu.Unlock() 33 | record.Offset = uint64(len(c.records)) // 추가할 위치 인덱스 == 오프셋 34 | c.records = append(c.records, record) 35 | return record.Offset, nil 36 | } 37 | 38 | // 39 | func (c *Log) Read(offset uint64) (Record, error) { 40 | c.mu.Lock() 41 | defer c.mu.Unlock() 42 | if offset >= uint64(len(c.records)) { 43 | return Record{}, ErrOffsetNotFound 44 | } 45 | return c.records[offset], nil 46 | } 47 | -------------------------------------------------------------------------------- /ch10-DeployLocally/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | uint64 term = 3; 8 | uint32 type = 4; 9 | } 10 | 11 | message ProduceRequest { 12 | Record record = 1; 13 | } 14 | 15 | message ProduceResponse { 16 | uint64 offset = 1; 17 | } 18 | 19 | message ConsumeRequest { 20 | uint64 offset = 1; 21 | } 22 | 23 | message ConsumeResponse { 24 | Record record = 1; 25 | } 26 | 27 | service Log { 28 | rpc Produce(ProduceRequest) returns (ProduceResponse) {} 29 | rpc Consume(ConsumeRequest) returns (ConsumeResponse) {} 30 | rpc ConsumeStream(ConsumeRequest) returns (stream ConsumeResponse) {} 31 | rpc ProduceStream(stream ProduceRequest) returns (stream ProduceResponse) {} 32 | rpc GetServers(GetServersRequest) returns (GetServersResponse) {} 33 | } 34 | 35 | message GetServersRequest {} 36 | 37 | message GetServersResponse { 38 | repeated Server servers = 1; 39 | } 40 | 41 | message Server { 42 | string id = 1; 43 | string rpc_addr = 2; 44 | bool is_leader = 3; 45 | } 46 | -------------------------------------------------------------------------------- /ch11-DeployCloud/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | uint64 term = 3; 8 | uint32 type = 4; 9 | } 10 | 11 | message ProduceRequest { 12 | Record record = 1; 13 | } 14 | 15 | message ProduceResponse { 16 | uint64 offset = 1; 17 | } 18 | 19 | message ConsumeRequest { 20 | uint64 offset = 1; 21 | } 22 | 23 | message ConsumeResponse { 24 | Record record = 1; 25 | } 26 | 27 | service Log { 28 | rpc Produce(ProduceRequest) returns (ProduceResponse) {} 29 | rpc Consume(ConsumeRequest) returns (ConsumeResponse) {} 30 | rpc ConsumeStream(ConsumeRequest) returns (stream ConsumeResponse) {} 31 | rpc ProduceStream(stream ProduceRequest) returns (stream ProduceResponse) {} 32 | rpc GetServers(GetServersRequest) returns (GetServersResponse) {} 33 | } 34 | 35 | message GetServersRequest {} 36 | 37 | message GetServersResponse { 38 | repeated Server servers = 1; 39 | } 40 | 41 | message Server { 42 | string id = 1; 43 | string rpc_addr = 2; 44 | bool is_leader = 3; 45 | } 46 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/api/v1/log.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package log.v1; 3 | option go_package = "github.com/nicewook/proglog/api/log_v1"; 4 | message Record { 5 | bytes value = 1; 6 | uint64 offset = 2; 7 | uint64 term = 3; 8 | uint32 type = 4; 9 | } 10 | 11 | message ProduceRequest { 12 | Record record = 1; 13 | } 14 | 15 | message ProduceResponse { 16 | uint64 offset = 1; 17 | } 18 | 19 | message ConsumeRequest { 20 | uint64 offset = 1; 21 | } 22 | 23 | message ConsumeResponse { 24 | Record record = 1; 25 | } 26 | 27 | service Log { 28 | rpc Produce(ProduceRequest) returns (ProduceResponse) {} 29 | rpc Consume(ConsumeRequest) returns (ConsumeResponse) {} 30 | rpc ConsumeStream(ConsumeRequest) returns (stream ConsumeResponse) {} 31 | rpc ProduceStream(stream ProduceRequest) returns (stream ProduceResponse) {} 32 | rpc GetServers(GetServersRequest) returns (GetServersResponse) {} 33 | } 34 | 35 | message GetServersRequest {} 36 | 37 | message GetServersResponse { 38 | repeated Server servers = 1; 39 | } 40 | 41 | message Server { 42 | string id = 1; 43 | string rpc_addr = 2; 44 | bool is_leader = 3; 45 | } 46 | -------------------------------------------------------------------------------- /ch10-DeployLocally/deploy/proglog/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: proglog 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/proglog/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: proglog 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/metacontroller/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: metacontroller 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/config/tls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | ) 9 | 10 | func SetupTLSConfig(cfg TLSConfig) (*tls.Config, error) { 11 | var err error 12 | tlsConfig := &tls.Config{} 13 | if cfg.CertFile != "" && cfg.KeyFile != "" { 14 | tlsConfig.Certificates = make([]tls.Certificate, 1) 15 | tlsConfig.Certificates[0], err = tls.LoadX509KeyPair( 16 | cfg.CertFile, 17 | cfg.KeyFile, 18 | ) 19 | if err != nil { 20 | return nil, err 21 | } 22 | } 23 | if cfg.CAFile != "" { 24 | b, err := ioutil.ReadFile(cfg.CAFile) 25 | if err != nil { 26 | return nil, err 27 | } 28 | ca := x509.NewCertPool() 29 | ok := ca.AppendCertsFromPEM([]byte(b)) 30 | if !ok { 31 | return nil, fmt.Errorf( 32 | "failed to parse root certificate: %q", 33 | cfg.CAFile, 34 | ) 35 | } 36 | if cfg.Server { 37 | tlsConfig.ClientCAs = ca 38 | tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 39 | } else { 40 | tlsConfig.RootCAs = ca 41 | } 42 | tlsConfig.ServerName = cfg.ServerAddress 43 | } 44 | return tlsConfig, nil 45 | } 46 | 47 | 48 | type TLSConfig struct { 49 | CertFile string 50 | KeyFile string 51 | CAFile string 52 | ServerAddress string 53 | Server bool 54 | } 55 | 56 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/config/tls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | ) 9 | 10 | func SetupTLSConfig(cfg TLSConfig) (*tls.Config, error) { 11 | var err error 12 | tlsConfig := &tls.Config{} 13 | if cfg.CertFile != "" && cfg.KeyFile != "" { 14 | tlsConfig.Certificates = make([]tls.Certificate, 1) 15 | tlsConfig.Certificates[0], err = tls.LoadX509KeyPair( 16 | cfg.CertFile, 17 | cfg.KeyFile, 18 | ) 19 | if err != nil { 20 | return nil, err 21 | } 22 | } 23 | if cfg.CAFile != "" { 24 | b, err := ioutil.ReadFile(cfg.CAFile) 25 | if err != nil { 26 | return nil, err 27 | } 28 | ca := x509.NewCertPool() 29 | ok := ca.AppendCertsFromPEM([]byte(b)) 30 | if !ok { 31 | return nil, fmt.Errorf( 32 | "failed to parse root certificate: %q", 33 | cfg.CAFile, 34 | ) 35 | } 36 | if cfg.Server { 37 | tlsConfig.ClientCAs = ca 38 | tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 39 | } else { 40 | tlsConfig.RootCAs = ca 41 | } 42 | tlsConfig.ServerName = cfg.ServerAddress 43 | } 44 | return tlsConfig, nil 45 | } 46 | 47 | 48 | type TLSConfig struct { 49 | CertFile string 50 | KeyFile string 51 | CAFile string 52 | ServerAddress string 53 | Server bool 54 | } 55 | 56 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/internal/config/tls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | ) 9 | 10 | func SetupTLSConfig(cfg TLSConfig) (*tls.Config, error) { 11 | var err error 12 | tlsConfig := &tls.Config{} 13 | if cfg.CertFile != "" && cfg.KeyFile != "" { 14 | tlsConfig.Certificates = make([]tls.Certificate, 1) 15 | tlsConfig.Certificates[0], err = tls.LoadX509KeyPair( 16 | cfg.CertFile, 17 | cfg.KeyFile, 18 | ) 19 | if err != nil { 20 | return nil, err 21 | } 22 | } 23 | if cfg.CAFile != "" { 24 | b, err := ioutil.ReadFile(cfg.CAFile) 25 | if err != nil { 26 | return nil, err 27 | } 28 | ca := x509.NewCertPool() 29 | ok := ca.AppendCertsFromPEM([]byte(b)) 30 | if !ok { 31 | return nil, fmt.Errorf( 32 | "failed to parse root certificate: %q", 33 | cfg.CAFile, 34 | ) 35 | } 36 | if cfg.Server { 37 | tlsConfig.ClientCAs = ca 38 | tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 39 | } else { 40 | tlsConfig.RootCAs = ca 41 | } 42 | tlsConfig.ServerName = cfg.ServerAddress 43 | } 44 | return tlsConfig, nil 45 | } 46 | 47 | 48 | type TLSConfig struct { 49 | CertFile string 50 | KeyFile string 51 | CAFile string 52 | ServerAddress string 53 | Server bool 54 | } 55 | 56 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/internal/config/tls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | ) 9 | 10 | func SetupTLSConfig(cfg TLSConfig) (*tls.Config, error) { 11 | var err error 12 | tlsConfig := &tls.Config{} 13 | if cfg.CertFile != "" && cfg.KeyFile != "" { 14 | tlsConfig.Certificates = make([]tls.Certificate, 1) 15 | tlsConfig.Certificates[0], err = tls.LoadX509KeyPair( 16 | cfg.CertFile, 17 | cfg.KeyFile, 18 | ) 19 | if err != nil { 20 | return nil, err 21 | } 22 | } 23 | if cfg.CAFile != "" { 24 | b, err := ioutil.ReadFile(cfg.CAFile) 25 | if err != nil { 26 | return nil, err 27 | } 28 | ca := x509.NewCertPool() 29 | ok := ca.AppendCertsFromPEM([]byte(b)) 30 | if !ok { 31 | return nil, fmt.Errorf( 32 | "failed to parse root certificate: %q", 33 | cfg.CAFile, 34 | ) 35 | } 36 | if cfg.Server { 37 | tlsConfig.ClientCAs = ca 38 | tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 39 | } else { 40 | tlsConfig.RootCAs = ca 41 | } 42 | tlsConfig.ServerName = cfg.ServerAddress 43 | } 44 | return tlsConfig, nil 45 | } 46 | 47 | 48 | type TLSConfig struct { 49 | CertFile string 50 | KeyFile string 51 | CAFile string 52 | ServerAddress string 53 | Server bool 54 | } 55 | 56 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/config/tls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | ) 9 | 10 | func SetupTLSConfig(cfg TLSConfig) (*tls.Config, error) { 11 | var err error 12 | tlsConfig := &tls.Config{} 13 | if cfg.CertFile != "" && cfg.KeyFile != "" { 14 | tlsConfig.Certificates = make([]tls.Certificate, 1) 15 | tlsConfig.Certificates[0], err = tls.LoadX509KeyPair( 16 | cfg.CertFile, 17 | cfg.KeyFile, 18 | ) 19 | if err != nil { 20 | return nil, err 21 | } 22 | } 23 | if cfg.CAFile != "" { 24 | b, err := ioutil.ReadFile(cfg.CAFile) 25 | if err != nil { 26 | return nil, err 27 | } 28 | ca := x509.NewCertPool() 29 | ok := ca.AppendCertsFromPEM([]byte(b)) 30 | if !ok { 31 | return nil, fmt.Errorf( 32 | "failed to parse root certificate: %q", 33 | cfg.CAFile, 34 | ) 35 | } 36 | if cfg.Server { 37 | tlsConfig.ClientCAs = ca 38 | tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 39 | } else { 40 | tlsConfig.RootCAs = ca 41 | } 42 | tlsConfig.ServerName = cfg.ServerAddress 43 | } 44 | return tlsConfig, nil 45 | } 46 | 47 | 48 | type TLSConfig struct { 49 | CertFile string 50 | KeyFile string 51 | CAFile string 52 | ServerAddress string 53 | Server bool 54 | } 55 | 56 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/config/tls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | ) 9 | 10 | func SetupTLSConfig(cfg TLSConfig) (*tls.Config, error) { 11 | var err error 12 | tlsConfig := &tls.Config{} 13 | if cfg.CertFile != "" && cfg.KeyFile != "" { 14 | tlsConfig.Certificates = make([]tls.Certificate, 1) 15 | tlsConfig.Certificates[0], err = tls.LoadX509KeyPair( 16 | cfg.CertFile, 17 | cfg.KeyFile, 18 | ) 19 | if err != nil { 20 | return nil, err 21 | } 22 | } 23 | if cfg.CAFile != "" { 24 | b, err := ioutil.ReadFile(cfg.CAFile) 25 | if err != nil { 26 | return nil, err 27 | } 28 | ca := x509.NewCertPool() 29 | ok := ca.AppendCertsFromPEM([]byte(b)) 30 | if !ok { 31 | return nil, fmt.Errorf( 32 | "failed to parse root certificate: %q", 33 | cfg.CAFile, 34 | ) 35 | } 36 | if cfg.Server { 37 | tlsConfig.ClientCAs = ca 38 | tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 39 | } else { 40 | tlsConfig.RootCAs = ca 41 | } 42 | tlsConfig.ServerName = cfg.ServerAddress 43 | } 44 | return tlsConfig, nil 45 | } 46 | 47 | 48 | type TLSConfig struct { 49 | CertFile string 50 | KeyFile string 51 | CAFile string 52 | ServerAddress string 53 | Server bool 54 | } 55 | 56 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/config/tls.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | ) 9 | 10 | func SetupTLSConfig(cfg TLSConfig) (*tls.Config, error) { 11 | var err error 12 | tlsConfig := &tls.Config{} 13 | if cfg.CertFile != "" && cfg.KeyFile != "" { 14 | tlsConfig.Certificates = make([]tls.Certificate, 1) 15 | tlsConfig.Certificates[0], err = tls.LoadX509KeyPair( 16 | cfg.CertFile, 17 | cfg.KeyFile, 18 | ) 19 | if err != nil { 20 | return nil, err 21 | } 22 | } 23 | if cfg.CAFile != "" { 24 | b, err := ioutil.ReadFile(cfg.CAFile) 25 | if err != nil { 26 | return nil, err 27 | } 28 | ca := x509.NewCertPool() 29 | ok := ca.AppendCertsFromPEM([]byte(b)) 30 | if !ok { 31 | return nil, fmt.Errorf( 32 | "failed to parse root certificate: %q", 33 | cfg.CAFile, 34 | ) 35 | } 36 | if cfg.Server { 37 | tlsConfig.ClientCAs = ca 38 | tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 39 | } else { 40 | tlsConfig.RootCAs = ca 41 | } 42 | tlsConfig.ServerName = cfg.ServerAddress 43 | } 44 | return tlsConfig, nil 45 | } 46 | 47 | 48 | type TLSConfig struct { 49 | CertFile string 50 | KeyFile string 51 | CAFile string 52 | ServerAddress string 53 | Server bool 54 | } 55 | 56 | -------------------------------------------------------------------------------- /ch2-StructureDataWithProtobuf/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 2 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 3 | github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM= 4 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 5 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= 6 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 7 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= 8 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 9 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= 10 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 11 | golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= 12 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 13 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 14 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 15 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /ch3-WriteALogPacakge/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/log/segment_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | api "github.com/nicewook/proglog/api/v1" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegment(t *testing.T) { 13 | dir, _ := os.MkdirTemp("", "segment-test") 14 | defer os.RemoveAll(dir) 15 | 16 | want := &api.Record{Value: []byte("hello world")} 17 | 18 | c := Config{} 19 | c.Segment.MaxStoreBytes = 1024 20 | c.Segment.MaxIndexBytes = entWidth * 3 21 | 22 | s, err := newSegment(dir, 16, c) 23 | require.NoError(t, err) 24 | require.Equal(t, uint64(16), s.nextOffset, s.nextOffset) 25 | require.False(t, s.IsMaxed()) 26 | 27 | for i := uint64(0); i < 3; i++ { 28 | off, err := s.Append(want) 29 | require.NoError(t, err) 30 | require.Equal(t, 16+i, off) 31 | 32 | got, err := s.Read(off) 33 | require.NoError(t, err) 34 | require.Equal(t, want.Value, got.Value) 35 | } 36 | 37 | _, err = s.Append(want) 38 | require.Equal(t, io.EOF, err) 39 | 40 | // maxed index 41 | require.True(t, s.IsMaxed()) 42 | 43 | c.Segment.MaxStoreBytes = uint64(len(want.Value) * 3) 44 | c.Segment.MaxIndexBytes = 1024 45 | 46 | s, err = newSegment(dir, 16, c) 47 | require.NoError(t, err) 48 | // maxed store 49 | require.True(t, s.IsMaxed()) 50 | 51 | err = s.Remove() 52 | require.NoError(t, err) 53 | s, err = newSegment(dir, 16, c) 54 | require.NoError(t, err) 55 | require.False(t, s.IsMaxed()) 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go 언어를 활용한 분산 서비스 개발 2 | 3 | ## 이 저장소에 대하여 4 | 5 | `Distributed services with Go`의 한글 번역서인 `Go 언어를 활용한 분산 서비스 개발`에서 패키지와 도구의 버전, 코드 내용의 변경사항 및 오류를 수정하여 실습한 내용을 정리하였다. `Go 언어를 활용한 분산 서비스 개발`에 실린 코드와 몇몇 차이가 있을 수 있으나 큰 틀에서는 책의 내용을 따른다. 6 | 7 | 각 챕터를 실습할 때에 해당 챕터를 go.work 파일에서 코멘트 해체하고 VSCode 에서 편집하면 편할 것이다. 8 | 9 | ## 책이 가지는 의미 10 | 11 | 분산 서비스를 Go로 구현하며 분산 서비스에 필요한 기능들과 작동의 전체적 개념을 이해하도록 도와준다. 12 | 13 | 책 자체는 두껍지 않지만 책이 소개하는 내용 전체를 소화해 내는 것은 쉽지 않을 것이다. 대상 독자를 중급 이상의 개발자로 보고 있기에 기본적인 사항은 알고 있거나 충분히 찾아서 할 수 있을 것이라 가정하고 있으며, 사용하는 패키지와 도구들인 protobuf, gRPC, raft, surf, docker, kind, helm등만 하여도 충분히 이해하려면 별도의 공부가 필요하기 때문이다. 14 | 15 | 그럼에도 불구하고, 분산 서비스라는 매력적이지만 접근하기 쉽지않은 주제에 대해 전체를 조망해볼 수 있다는 점에서 이 책은 좋은 가이드가 될 것이다. 16 | 17 | ## 기타 안내사항 18 | 19 | - install.md: 챕터 별 필요한 도구와 패키지들의 설치 방법 정리 20 | - 번역서 구매 링크: {추후 업데이트} 21 | - 원서 웹페이지 링크: https://pragprog.com/titles/tjgo/distributed-services-with-go/ 22 | - 책에 대한 정보와 소스코드를 얻을 수 있다. 23 | - 원서 코드의 오류를 바로잡는데 도움이 되었던 링크들 24 | - 오류 수정 게시물: https://forum.devtalk.com/t/distributed-services-with-go-unable-to-pass-readiness-liveness-checks-page-210-215/22354/3 25 | - 게시물의 수정 코드: https://github.com/varunbpatil/proglog/commit/2ecd0d7812b40583b76594eb148c1b4e60ca835b 26 | - 원서를 실습한 또 다른 GitHub repo: https://github.com/evdzhurov/dist-services-with-go/ 27 | 28 | ## 번역을 하며 받았던 도움에 감사 인사 29 | 30 | - 한결같은 지지와 배려를 해준 아내와 아빠의 에너지를 채워준 두 아들 31 | - 이 책의 번역을 소개해주신 번역 선배 김찬빈님 32 | - 여유로운 가이드로 안전감있게 번역할 수 있도록 챙겨주신 제이펍 이상복 팀장님 33 | - 원서 코드의 핵심 오류를 잡아내주신 권경모님 34 | - 다양한 생각과 경험을 나누며 언제나 영감의 원천이 되는 커뮤니티들인 슬랙 <딥백수>와 디스코드 <코딩냄비> 여러분들! 35 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch3-WriteALogPacakge/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/log/index_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestIndex(t *testing.T) { 12 | f, err := os.CreateTemp(os.TempDir(), "index_test") 13 | require.NoError(t, err) 14 | defer os.Remove(f.Name()) 15 | 16 | c := Config{} 17 | c.Segment.MaxIndexBytes = 1024 18 | idx, err := newIndex(f, c) 19 | require.NoError(t, err) 20 | _, _, err = idx.Read(-1) // -1을 읽으라 하면 에러가 나야 한다. 21 | require.Error(t, err) 22 | require.Equal(t, f.Name(), idx.Name()) // index 만들때 파일명이 idx.Name()으로 읽혀야 한다. 23 | entries := []struct { 24 | Off uint32 25 | Pos uint64 26 | }{ 27 | {Off: 0, Pos: 0}, // Offset 0번의 첫 바이트 위치가 0 28 | {Off: 1, Pos: 10}, // Offset 1번의 첫 바이트 위치가 10 29 | } 30 | 31 | for _, want := range entries { 32 | err = idx.Write(want.Off, want.Pos) 33 | require.NoError(t, err) 34 | 35 | _, pos, err := idx.Read(int64(want.Off)) 36 | require.NoError(t, err) 37 | require.Equal(t, want.Pos, pos) // 쓴걸 다시 읽어보고 확인 38 | } 39 | 40 | // 존재 항목 넘어서 읽으려 하면 에러 나야 한다. 41 | _, _, err = idx.Read(int64(len(entries))) // 최대 인덱스는 len(entires) -1이다. 42 | require.Equal(t, io.EOF, err) // 특히나 에러가 EOF 이어야 한다. 43 | _ = idx.Close() 44 | 45 | // 파일이 이미 있다면 파일의 정보를 바탕으로 index를 만들어야 한다. 46 | f, _ = os.OpenFile(f.Name(), os.O_RDWR, 0600) 47 | idx, err = newIndex(f, c) 48 | require.NoError(t, err) 49 | off, pos, err := idx.Read(-1) // 가장 마지막 읽기 50 | require.NoError(t, err) 51 | require.Equal(t, uint32(1), off) 52 | require.Equal(t, entries[1].Pos, pos) 53 | } 54 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/casbin/casbin v1.9.1 7 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 8 | github.com/stretchr/testify v1.7.1 9 | github.com/tysonmote/gommap v0.0.2 10 | google.golang.org/genproto v0.0.0-20220623142657-077d458a5694 11 | google.golang.org/grpc v1.47.0 12 | google.golang.org/protobuf v1.28.0 13 | ) 14 | 15 | require ( 16 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect 17 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/golang/protobuf v1.5.2 // indirect 20 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect 21 | github.com/hashicorp/errwrap v1.0.0 // indirect 22 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 23 | github.com/hashicorp/go-msgpack v0.5.3 // indirect 24 | github.com/hashicorp/go-multierror v1.1.0 // indirect 25 | github.com/hashicorp/go-sockaddr v1.0.0 // indirect 26 | github.com/hashicorp/golang-lru v0.5.0 // indirect 27 | github.com/hashicorp/memberlist v0.3.0 // indirect 28 | github.com/hashicorp/serf v0.9.8 // indirect 29 | github.com/miekg/dns v1.1.41 // indirect 30 | github.com/pmezard/go-difflib v1.0.0 // indirect 31 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect 32 | github.com/travisjeffery/go-dynaport v1.0.0 // indirect 33 | go.opencensus.io v0.23.0 // indirect 34 | golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 // indirect 35 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect 36 | golang.org/x/text v0.3.6 // indirect 37 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/metacontroller/templates/metacontroller-rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: metacontroller 5 | namespace: metacontroller 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | name: metacontroller 11 | rules: 12 | - apiGroups: 13 | - "*" 14 | resources: 15 | - "*" 16 | verbs: 17 | - "*" 18 | --- 19 | apiVersion: rbac.authorization.k8s.io/v1 20 | kind: ClusterRoleBinding 21 | metadata: 22 | name: metacontroller 23 | subjects: 24 | - kind: ServiceAccount 25 | name: metacontroller 26 | namespace: metacontroller 27 | roleRef: 28 | kind: ClusterRole 29 | name: metacontroller 30 | apiGroup: rbac.authorization.k8s.io 31 | --- 32 | kind: ClusterRole 33 | apiVersion: rbac.authorization.k8s.io/v1 34 | metadata: 35 | name: aggregate-metacontroller-view 36 | labels: 37 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 38 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 39 | rbac.authorization.k8s.io/aggregate-to-view: "true" 40 | rules: 41 | - apiGroups: 42 | - metacontroller.k8s.io 43 | resources: 44 | - compositecontrollers 45 | - controllerrevisions 46 | - decoratorcontrollers 47 | verbs: 48 | - get 49 | - list 50 | - watch 51 | --- 52 | kind: ClusterRole 53 | apiVersion: rbac.authorization.k8s.io/v1 54 | metadata: 55 | name: aggregate-metacontroller-edit 56 | labels: 57 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 58 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 59 | rules: 60 | - apiGroups: 61 | - metacontroller.k8s.io 62 | resources: 63 | - controllerrevisions 64 | verbs: 65 | - create 66 | - delete 67 | - deletecollection 68 | - get 69 | - list 70 | - patch 71 | - update 72 | - watch 73 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/loadbalance/picker.go: -------------------------------------------------------------------------------- 1 | package loadbalance 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "sync/atomic" 7 | 8 | "google.golang.org/grpc/balancer" 9 | "google.golang.org/grpc/balancer/base" 10 | ) 11 | 12 | var _ base.PickerBuilder = (*Picker)(nil) 13 | 14 | type Picker struct { 15 | mu sync.RWMutex 16 | leader balancer.SubConn 17 | followers []balancer.SubConn 18 | current uint64 19 | } 20 | 21 | func (p *Picker) Build(buildInfo base.PickerBuildInfo) balancer.Picker { 22 | p.mu.Lock() 23 | defer p.mu.Unlock() 24 | var followers []balancer.SubConn 25 | for sc, scInfo := range buildInfo.ReadySCs { 26 | isLeader := scInfo. 27 | Address. 28 | Attributes. 29 | Value("is_leader").(bool) 30 | if isLeader { 31 | p.leader = sc 32 | continue 33 | } 34 | followers = append(followers, sc) 35 | } 36 | p.followers = followers 37 | return p 38 | } 39 | 40 | 41 | var _ balancer.Picker = (*Picker)(nil) 42 | 43 | func (p *Picker) Pick(info balancer.PickInfo) ( 44 | balancer.PickResult, error) { 45 | p.mu.RLock() 46 | defer p.mu.RUnlock() 47 | var result balancer.PickResult 48 | if strings.Contains(info.FullMethodName, "Produce") || 49 | len(p.followers) == 0 { 50 | result.SubConn = p.leader 51 | } else if strings.Contains(info.FullMethodName, "Consume") { 52 | result.SubConn = p.nextFollower() 53 | } 54 | if result.SubConn == nil { 55 | return result, balancer.ErrNoSubConnAvailable 56 | } 57 | return result, nil 58 | } 59 | 60 | func (p *Picker) nextFollower() balancer.SubConn { 61 | cur := atomic.AddUint64(&p.current, uint64(1)) 62 | len := uint64(len(p.followers)) 63 | idx := int(cur % len) 64 | return p.followers[idx] 65 | } 66 | 67 | 68 | func init() { 69 | balancer.Register( 70 | base.NewBalancerBuilder(Name, &Picker{}, base.Config{}), 71 | ) 72 | } 73 | 74 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/loadbalance/picker.go: -------------------------------------------------------------------------------- 1 | package loadbalance 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "sync/atomic" 7 | 8 | "google.golang.org/grpc/balancer" 9 | "google.golang.org/grpc/balancer/base" 10 | ) 11 | 12 | var _ base.PickerBuilder = (*Picker)(nil) 13 | 14 | type Picker struct { 15 | mu sync.RWMutex 16 | leader balancer.SubConn 17 | followers []balancer.SubConn 18 | current uint64 19 | } 20 | 21 | func (p *Picker) Build(buildInfo base.PickerBuildInfo) balancer.Picker { 22 | p.mu.Lock() 23 | defer p.mu.Unlock() 24 | var followers []balancer.SubConn 25 | for sc, scInfo := range buildInfo.ReadySCs { 26 | isLeader := scInfo. 27 | Address. 28 | Attributes. 29 | Value("is_leader").(bool) 30 | if isLeader { 31 | p.leader = sc 32 | continue 33 | } 34 | followers = append(followers, sc) 35 | } 36 | p.followers = followers 37 | return p 38 | } 39 | 40 | 41 | var _ balancer.Picker = (*Picker)(nil) 42 | 43 | func (p *Picker) Pick(info balancer.PickInfo) ( 44 | balancer.PickResult, error) { 45 | p.mu.RLock() 46 | defer p.mu.RUnlock() 47 | var result balancer.PickResult 48 | if strings.Contains(info.FullMethodName, "Produce") || 49 | len(p.followers) == 0 { 50 | result.SubConn = p.leader 51 | } else if strings.Contains(info.FullMethodName, "Consume") { 52 | result.SubConn = p.nextFollower() 53 | } 54 | if result.SubConn == nil { 55 | return result, balancer.ErrNoSubConnAvailable 56 | } 57 | return result, nil 58 | } 59 | 60 | func (p *Picker) nextFollower() balancer.SubConn { 61 | cur := atomic.AddUint64(&p.current, uint64(1)) 62 | len := uint64(len(p.followers)) 63 | idx := int(cur % len) 64 | return p.followers[idx] 65 | } 66 | 67 | 68 | func init() { 69 | balancer.Register( 70 | base.NewBalancerBuilder(Name, &Picker{}, base.Config{}), 71 | ) 72 | } 73 | 74 | -------------------------------------------------------------------------------- /ch10-DeployLocally/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_PATH=${HOME}/.proglog/ 2 | 3 | .PHONY: init 4 | init: 5 | mkdir -p ${CONFIG_PATH} 6 | 7 | .PHONY: gencert 8 | gencert: 9 | cfssl gencert \ 10 | -initca test/ca-csr.json | cfssljson -bare ca 11 | 12 | cfssl gencert \ 13 | -ca=ca.pem \ 14 | -ca-key=ca-key.pem \ 15 | -config=test/ca-config.json \ 16 | -profile=server \ 17 | test/server-csr.json | cfssljson -bare server 18 | 19 | # 클라이언트 인증서 20 | cfssl gencert \ 21 | -ca=ca.pem \ 22 | -ca-key=ca-key.pem \ 23 | -config=test/ca-config.json \ 24 | -profile=client \ 25 | test/client-csr.json | cfssljson -bare client 26 | 27 | # 여러 클라이언트 인증서 28 | cfssl gencert \ 29 | -ca=ca.pem \ 30 | -ca-key=ca-key.pem \ 31 | -config=test/ca-config.json \ 32 | -profile=client \ 33 | -cn="root" \ 34 | test/client-csr.json | cfssljson -bare root-client 35 | 36 | cfssl gencert \ 37 | -ca=ca.pem \ 38 | -ca-key=ca-key.pem \ 39 | -config=test/ca-config.json \ 40 | -profile=client \ 41 | -cn="nobody" \ 42 | test/client-csr.json | cfssljson -bare nobody-client 43 | 44 | mv *.pem *.csr ${CONFIG_PATH} 45 | 46 | $(CONFIG_PATH)/model.conf: 47 | cp test/model.conf $(CONFIG_PATH)/model.conf 48 | 49 | $(CONFIG_PATH)/policy.csv: 50 | cp test/policy.csv $(CONFIG_PATH)/policy.csv 51 | 52 | # 테스트 53 | .PHONY: test 54 | test: 55 | test: $(CONFIG_PATH)/policy.csv $(CONFIG_PATH)/model.conf 56 | go test -race ./... 57 | 58 | # protobuf 컴파일 59 | .PHONY: compile 60 | compile: 61 | protoc api/v1/*.proto \ 62 | --go_out=. \ 63 | --go-grpc_out=. \ 64 | --go_opt=paths=source_relative \ 65 | --go-grpc_opt=paths=source_relative \ 66 | --proto_path=. 67 | 68 | # 도커 빌드 69 | TAG ?= 0.0.1 70 | 71 | build-docker: 72 | docker build -t github.com/nicewook/proglog:$(TAG) . 73 | 74 | load-docker: 75 | kind load docker-image github.com/nicewook/proglog:$(TAG) 76 | 77 | -------------------------------------------------------------------------------- /ch11-DeployCloud/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_PATH=${HOME}/.proglog/ 2 | 3 | .PHONY: init 4 | init: 5 | mkdir -p ${CONFIG_PATH} 6 | 7 | .PHONY: gencert 8 | gencert: 9 | cfssl gencert \ 10 | -initca test/ca-csr.json | cfssljson -bare ca 11 | 12 | cfssl gencert \ 13 | -ca=ca.pem \ 14 | -ca-key=ca-key.pem \ 15 | -config=test/ca-config.json \ 16 | -profile=server \ 17 | test/server-csr.json | cfssljson -bare server 18 | 19 | # 클라이언트 인증서 20 | cfssl gencert \ 21 | -ca=ca.pem \ 22 | -ca-key=ca-key.pem \ 23 | -config=test/ca-config.json \ 24 | -profile=client \ 25 | test/client-csr.json | cfssljson -bare client 26 | 27 | # 여러 클라이언트 인증서 28 | cfssl gencert \ 29 | -ca=ca.pem \ 30 | -ca-key=ca-key.pem \ 31 | -config=test/ca-config.json \ 32 | -profile=client \ 33 | -cn="root" \ 34 | test/client-csr.json | cfssljson -bare root-client 35 | 36 | cfssl gencert \ 37 | -ca=ca.pem \ 38 | -ca-key=ca-key.pem \ 39 | -config=test/ca-config.json \ 40 | -profile=client \ 41 | -cn="nobody" \ 42 | test/client-csr.json | cfssljson -bare nobody-client 43 | 44 | mv *.pem *.csr ${CONFIG_PATH} 45 | 46 | $(CONFIG_PATH)/model.conf: 47 | cp test/model.conf $(CONFIG_PATH)/model.conf 48 | 49 | $(CONFIG_PATH)/policy.csv: 50 | cp test/policy.csv $(CONFIG_PATH)/policy.csv 51 | 52 | # 테스트 53 | .PHONY: test 54 | test: 55 | test: $(CONFIG_PATH)/policy.csv $(CONFIG_PATH)/model.conf 56 | go test -race ./... 57 | 58 | # protobuf 컴파일 59 | .PHONY: compile 60 | compile: 61 | protoc api/v1/*.proto \ 62 | --go_out=. \ 63 | --go-grpc_out=. \ 64 | --go_opt=paths=source_relative \ 65 | --go-grpc_opt=paths=source_relative \ 66 | --proto_path=. 67 | 68 | # 도커 빌드 69 | TAG ?= 0.0.1 70 | 71 | build-docker: 72 | docker build -t github.com/nicewook/proglog:$(TAG) . 73 | 74 | load-docker: 75 | kind load docker-image github.com/nicewook/proglog:$(TAG) 76 | 77 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/loadbalance/picker.go: -------------------------------------------------------------------------------- 1 | package loadbalance 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "sync/atomic" 7 | 8 | "google.golang.org/grpc/balancer" 9 | "google.golang.org/grpc/balancer/base" 10 | ) 11 | 12 | var _ base.PickerBuilder = (*Picker)(nil) 13 | 14 | type Picker struct { 15 | mu sync.RWMutex 16 | leader balancer.SubConn 17 | followers []balancer.SubConn 18 | current uint64 19 | } 20 | 21 | func (p *Picker) Build(buildInfo base.PickerBuildInfo) balancer.Picker { 22 | p.mu.Lock() 23 | defer p.mu.Unlock() 24 | var followers []balancer.SubConn 25 | for sc, scInfo := range buildInfo.ReadySCs { 26 | isLeader := scInfo. 27 | Address. 28 | Attributes. 29 | Value("is_leader").(bool) 30 | if isLeader { 31 | p.leader = sc 32 | continue 33 | } 34 | followers = append(followers, sc) 35 | } 36 | p.followers = followers 37 | return p 38 | } 39 | 40 | 41 | var _ balancer.Picker = (*Picker)(nil) 42 | 43 | func (p *Picker) Pick(info balancer.PickInfo) ( 44 | balancer.PickResult, error) { 45 | p.mu.RLock() 46 | defer p.mu.RUnlock() 47 | var result balancer.PickResult 48 | if strings.Contains(info.FullMethodName, "Produce") || 49 | len(p.followers) == 0 { 50 | result.SubConn = p.leader 51 | } else if strings.Contains(info.FullMethodName, "Consume") { 52 | result.SubConn = p.nextFollower() 53 | } 54 | if result.SubConn == nil { 55 | return result, balancer.ErrNoSubConnAvailable 56 | } 57 | return result, nil 58 | } 59 | 60 | func (p *Picker) nextFollower() balancer.SubConn { 61 | cur := atomic.AddUint64(&p.current, uint64(1)) 62 | len := uint64(len(p.followers)) 63 | idx := int(cur % len) 64 | return p.followers[idx] 65 | } 66 | 67 | 68 | func init() { 69 | balancer.Register( 70 | base.NewBalancerBuilder(Name, &Picker{}, base.Config{}), 71 | ) 72 | } 73 | 74 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/Makefile: -------------------------------------------------------------------------------- 1 | # START: begin 2 | CONFIG_PATH=${HOME}/.proglog/ 3 | 4 | .PHONY: init 5 | init: 6 | mkdir -p ${CONFIG_PATH} 7 | 8 | .PHONY: gencert 9 | gencert: 10 | cfssl gencert \ 11 | -initca test/ca-csr.json | cfssljson -bare ca 12 | 13 | cfssl gencert \ 14 | -ca=ca.pem \ 15 | -ca-key=ca-key.pem \ 16 | -config=test/ca-config.json \ 17 | -profile=server \ 18 | test/server-csr.json | cfssljson -bare server 19 | # END: begin 20 | 21 | # START: client 22 | cfssl gencert \ 23 | -ca=ca.pem \ 24 | -ca-key=ca-key.pem \ 25 | -config=test/ca-config.json \ 26 | -profile=client \ 27 | test/client-csr.json | cfssljson -bare client 28 | # END: client 29 | 30 | # START: multi_client 31 | cfssl gencert \ 32 | -ca=ca.pem \ 33 | -ca-key=ca-key.pem \ 34 | -config=test/ca-config.json \ 35 | -profile=client \ 36 | -cn="root" \ 37 | test/client-csr.json | cfssljson -bare root-client 38 | 39 | cfssl gencert \ 40 | -ca=ca.pem \ 41 | -ca-key=ca-key.pem \ 42 | -config=test/ca-config.json \ 43 | -profile=client \ 44 | -cn="nobody" \ 45 | test/client-csr.json | cfssljson -bare nobody-client 46 | # END: multi_client 47 | 48 | # START: begin 49 | mv *.pem *.csr ${CONFIG_PATH} 50 | 51 | # END: begin 52 | # START: auth 53 | $(CONFIG_PATH)/model.conf: 54 | cp test/model.conf $(CONFIG_PATH)/model.conf 55 | 56 | $(CONFIG_PATH)/policy.csv: 57 | cp test/policy.csv $(CONFIG_PATH)/policy.csv 58 | 59 | # START: begin 60 | .PHONY: test 61 | # END: auth 62 | test: 63 | # END: begin 64 | # START: auth 65 | test: $(CONFIG_PATH)/policy.csv $(CONFIG_PATH)/model.conf 66 | #: START: begin 67 | go test -race ./... 68 | # END: auth 69 | 70 | .PHONY: compile 71 | compile: 72 | protoc api/v1/*.proto \ 73 | --go_out=. \ 74 | --go-grpc_out=. \ 75 | --go_opt=paths=source_relative \ 76 | --go-grpc_opt=paths=source_relative \ 77 | --proto_path=. 78 | 79 | # END: begin 80 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/Makefile: -------------------------------------------------------------------------------- 1 | # START: begin 2 | CONFIG_PATH=${HOME}/.proglog/ 3 | 4 | .PHONY: init 5 | init: 6 | mkdir -p ${CONFIG_PATH} 7 | 8 | .PHONY: gencert 9 | gencert: 10 | cfssl gencert \ 11 | -initca test/ca-csr.json | cfssljson -bare ca 12 | 13 | cfssl gencert \ 14 | -ca=ca.pem \ 15 | -ca-key=ca-key.pem \ 16 | -config=test/ca-config.json \ 17 | -profile=server \ 18 | test/server-csr.json | cfssljson -bare server 19 | # END: begin 20 | 21 | # START: client 22 | cfssl gencert \ 23 | -ca=ca.pem \ 24 | -ca-key=ca-key.pem \ 25 | -config=test/ca-config.json \ 26 | -profile=client \ 27 | test/client-csr.json | cfssljson -bare client 28 | # END: client 29 | 30 | # START: multi_client 31 | cfssl gencert \ 32 | -ca=ca.pem \ 33 | -ca-key=ca-key.pem \ 34 | -config=test/ca-config.json \ 35 | -profile=client \ 36 | -cn="root" \ 37 | test/client-csr.json | cfssljson -bare root-client 38 | 39 | cfssl gencert \ 40 | -ca=ca.pem \ 41 | -ca-key=ca-key.pem \ 42 | -config=test/ca-config.json \ 43 | -profile=client \ 44 | -cn="nobody" \ 45 | test/client-csr.json | cfssljson -bare nobody-client 46 | # END: multi_client 47 | 48 | # START: begin 49 | mv *.pem *.csr ${CONFIG_PATH} 50 | 51 | # END: begin 52 | # START: auth 53 | $(CONFIG_PATH)/model.conf: 54 | cp test/model.conf $(CONFIG_PATH)/model.conf 55 | 56 | $(CONFIG_PATH)/policy.csv: 57 | cp test/policy.csv $(CONFIG_PATH)/policy.csv 58 | 59 | # START: begin 60 | .PHONY: test 61 | # END: auth 62 | test: 63 | # END: begin 64 | # START: auth 65 | test: $(CONFIG_PATH)/policy.csv $(CONFIG_PATH)/model.conf 66 | #: START: begin 67 | go test -race ./... 68 | # END: auth 69 | 70 | .PHONY: compile 71 | compile: 72 | protoc api/v1/*.proto \ 73 | --go_out=. \ 74 | --go-grpc_out=. \ 75 | --go_opt=paths=source_relative \ 76 | --go-grpc_opt=paths=source_relative \ 77 | --proto_path=. 78 | 79 | # END: begin 80 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/Makefile: -------------------------------------------------------------------------------- 1 | # START: begin 2 | CONFIG_PATH=${HOME}/.proglog/ 3 | 4 | .PHONY: init 5 | init: 6 | mkdir -p ${CONFIG_PATH} 7 | 8 | .PHONY: gencert 9 | gencert: 10 | cfssl gencert \ 11 | -initca test/ca-csr.json | cfssljson -bare ca 12 | 13 | cfssl gencert \ 14 | -ca=ca.pem \ 15 | -ca-key=ca-key.pem \ 16 | -config=test/ca-config.json \ 17 | -profile=server \ 18 | test/server-csr.json | cfssljson -bare server 19 | # END: begin 20 | 21 | # START: client 22 | cfssl gencert \ 23 | -ca=ca.pem \ 24 | -ca-key=ca-key.pem \ 25 | -config=test/ca-config.json \ 26 | -profile=client \ 27 | test/client-csr.json | cfssljson -bare client 28 | # END: client 29 | 30 | # START: multi_client 31 | cfssl gencert \ 32 | -ca=ca.pem \ 33 | -ca-key=ca-key.pem \ 34 | -config=test/ca-config.json \ 35 | -profile=client \ 36 | -cn="root" \ 37 | test/client-csr.json | cfssljson -bare root-client 38 | 39 | cfssl gencert \ 40 | -ca=ca.pem \ 41 | -ca-key=ca-key.pem \ 42 | -config=test/ca-config.json \ 43 | -profile=client \ 44 | -cn="nobody" \ 45 | test/client-csr.json | cfssljson -bare nobody-client 46 | # END: multi_client 47 | 48 | # START: begin 49 | mv *.pem *.csr ${CONFIG_PATH} 50 | 51 | # END: begin 52 | # START: auth 53 | $(CONFIG_PATH)/model.conf: 54 | cp test/model.conf $(CONFIG_PATH)/model.conf 55 | 56 | $(CONFIG_PATH)/policy.csv: 57 | cp test/policy.csv $(CONFIG_PATH)/policy.csv 58 | 59 | # START: begin 60 | .PHONY: test 61 | # END: auth 62 | test: 63 | # END: begin 64 | # START: auth 65 | test: $(CONFIG_PATH)/policy.csv $(CONFIG_PATH)/model.conf 66 | #: START: begin 67 | go test -race ./... 68 | # END: auth 69 | 70 | .PHONY: compile 71 | compile: 72 | protoc api/v1/*.proto \ 73 | --go_out=. \ 74 | --go-grpc_out=. \ 75 | --go_opt=paths=source_relative \ 76 | --go-grpc_opt=paths=source_relative \ 77 | --proto_path=. 78 | 79 | # END: begin 80 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/proglog/templates/service-per-pod.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.service.lb }} 2 | apiVersion: metacontroller.k8s.io/v1alpha1 3 | kind: DecoratorController 4 | metadata: 5 | name: service-per-pod 6 | spec: 7 | resources: 8 | - apiVersion: apps/v1 9 | resource: statefulsets 10 | annotationSelector: 11 | matchExpressions: 12 | - {key: service-per-pod-label, operator: Exists} 13 | - {key: service-per-pod-ports, operator: Exists} 14 | attachments: 15 | - apiVersion: v1 16 | resource: services 17 | hooks: 18 | sync: 19 | webhook: 20 | url: "http://service-per-pod.metacontroller/create-service-per-pod" 21 | finalize: 22 | webhook: 23 | url: "http://service-per-pod.metacontroller/delete-service-per-pod" 24 | --- 25 | apiVersion: v1 26 | kind: ConfigMap 27 | metadata: 28 | namespace: metacontroller 29 | name: service-per-pod-hooks 30 | data: 31 | {{ (.Files.Glob "hooks/*").AsConfig | indent 2 }} 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: service-per-pod 37 | namespace: metacontroller 38 | spec: 39 | replicas: 1 40 | selector: 41 | matchLabels: 42 | app: service-per-pod 43 | template: 44 | metadata: 45 | labels: 46 | app: service-per-pod 47 | spec: 48 | containers: 49 | - name: hooks 50 | image: metacontroller/jsonnetd:0.1 51 | imagePullPolicy: Always 52 | workingDir: /hooks 53 | volumeMounts: 54 | - name: hooks 55 | mountPath: /hooks 56 | volumes: 57 | - name: hooks 58 | configMap: 59 | name: service-per-pod-hooks 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: service-per-pod 65 | namespace: metacontroller 66 | spec: 67 | selector: 68 | app: service-per-pod 69 | ports: 70 | - port: 80 71 | targetPort: 8080 72 | {{ end }} 73 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/Makefile: -------------------------------------------------------------------------------- 1 | # START: begin 2 | CONFIG_PATH=${HOME}/.proglog/ 3 | 4 | .PHONY: init 5 | init: 6 | mkdir -p ${CONFIG_PATH} 7 | 8 | .PHONY: gencert 9 | gencert: 10 | cfssl gencert \ 11 | -initca test/ca-csr.json | cfssljson -bare ca 12 | 13 | cfssl gencert \ 14 | -ca=ca.pem \ 15 | -ca-key=ca-key.pem \ 16 | -config=test/ca-config.json \ 17 | -profile=server \ 18 | test/server-csr.json | cfssljson -bare server 19 | # END: begin 20 | 21 | # START: client 22 | cfssl gencert \ 23 | -ca=ca.pem \ 24 | -ca-key=ca-key.pem \ 25 | -config=test/ca-config.json \ 26 | -profile=client \ 27 | test/client-csr.json | cfssljson -bare client 28 | # END: client 29 | 30 | # START: multi_client 31 | cfssl gencert \ 32 | -ca=ca.pem \ 33 | -ca-key=ca-key.pem \ 34 | -config=test/ca-config.json \ 35 | -profile=client \ 36 | -cn="root" \ 37 | test/client-csr.json | cfssljson -bare root-client 38 | 39 | cfssl gencert \ 40 | -ca=ca.pem \ 41 | -ca-key=ca-key.pem \ 42 | -config=test/ca-config.json \ 43 | -profile=client \ 44 | -cn="nobody" \ 45 | test/client-csr.json | cfssljson -bare nobody-client 46 | # END: multi_client 47 | 48 | # START: begin 49 | mv *.pem *.csr ${CONFIG_PATH} 50 | 51 | # END: begin 52 | # START: auth 53 | $(CONFIG_PATH)/model.conf: 54 | cp test/model.conf $(CONFIG_PATH)/model.conf 55 | 56 | $(CONFIG_PATH)/policy.csv: 57 | cp test/policy.csv $(CONFIG_PATH)/policy.csv 58 | 59 | # START: begin 60 | .PHONY: test 61 | # END: auth 62 | test: 63 | # END: begin 64 | # START: auth 65 | test: $(CONFIG_PATH)/policy.csv $(CONFIG_PATH)/model.conf 66 | #: START: begin 67 | go test -race ./... 68 | # END: auth 69 | 70 | .PHONY: compile 71 | compile: 72 | protoc api/v1/*.proto \ 73 | --go_out=. \ 74 | --go-grpc_out=. \ 75 | --go_opt=paths=source_relative \ 76 | --go-grpc_opt=paths=source_relative \ 77 | --proto_path=. 78 | 79 | # END: begin 80 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/Makefile: -------------------------------------------------------------------------------- 1 | # START: begin 2 | CONFIG_PATH=${HOME}/.proglog/ 3 | 4 | .PHONY: init 5 | init: 6 | mkdir -p ${CONFIG_PATH} 7 | 8 | .PHONY: gencert 9 | gencert: 10 | cfssl gencert \ 11 | -initca test/ca-csr.json | cfssljson -bare ca 12 | 13 | cfssl gencert \ 14 | -ca=ca.pem \ 15 | -ca-key=ca-key.pem \ 16 | -config=test/ca-config.json \ 17 | -profile=server \ 18 | test/server-csr.json | cfssljson -bare server 19 | # END: begin 20 | 21 | # START: client 22 | cfssl gencert \ 23 | -ca=ca.pem \ 24 | -ca-key=ca-key.pem \ 25 | -config=test/ca-config.json \ 26 | -profile=client \ 27 | test/client-csr.json | cfssljson -bare client 28 | # END: client 29 | 30 | # START: multi_client 31 | cfssl gencert \ 32 | -ca=ca.pem \ 33 | -ca-key=ca-key.pem \ 34 | -config=test/ca-config.json \ 35 | -profile=client \ 36 | -cn="root" \ 37 | test/client-csr.json | cfssljson -bare root-client 38 | 39 | cfssl gencert \ 40 | -ca=ca.pem \ 41 | -ca-key=ca-key.pem \ 42 | -config=test/ca-config.json \ 43 | -profile=client \ 44 | -cn="nobody" \ 45 | test/client-csr.json | cfssljson -bare nobody-client 46 | # END: multi_client 47 | 48 | # START: begin 49 | mv *.pem *.csr ${CONFIG_PATH} 50 | 51 | # END: begin 52 | # START: auth 53 | $(CONFIG_PATH)/model.conf: 54 | cp test/model.conf $(CONFIG_PATH)/model.conf 55 | 56 | $(CONFIG_PATH)/policy.csv: 57 | cp test/policy.csv $(CONFIG_PATH)/policy.csv 58 | 59 | # START: begin 60 | .PHONY: test 61 | # END: auth 62 | test: 63 | # END: begin 64 | # START: auth 65 | test: $(CONFIG_PATH)/policy.csv $(CONFIG_PATH)/model.conf 66 | #: START: begin 67 | go test -race ./... 68 | # END: auth 69 | 70 | .PHONY: compile 71 | compile: 72 | protoc api/v1/*.proto \ 73 | --go_out=. \ 74 | --go-grpc_out=. \ 75 | --go_opt=paths=source_relative \ 76 | --go-grpc_opt=paths=source_relative \ 77 | --proto_path=. 78 | 79 | # END: begin 80 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch3-WriteALogPacakge/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/log/store.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | enc = binary.BigEndian 12 | ) 13 | 14 | const ( 15 | lenWidth = 8 16 | ) 17 | 18 | type store struct { 19 | *os.File 20 | mu sync.Mutex 21 | buf *bufio.Writer 22 | size uint64 23 | } 24 | 25 | func newStore(f *os.File) (*store, error) { 26 | fi, err := os.Stat(f.Name()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | size := uint64(fi.Size()) 31 | return &store{ 32 | File: f, 33 | size: size, 34 | buf: bufio.NewWriter(f), 35 | }, nil 36 | } 37 | 38 | func (s *store) Append(p []byte) (n uint64, pos uint64, err error) { 39 | s.mu.Lock() 40 | defer s.mu.Unlock() 41 | pos = s.size 42 | if err := binary.Write(s.buf, enc, uint64(len(p))); err != nil { 43 | return 0, 0, err 44 | } 45 | w, err := s.buf.Write(p) 46 | if err != nil { 47 | return 0, 0, err 48 | } 49 | w += lenWidth 50 | s.size += uint64(w) 51 | return uint64(w), pos, nil 52 | } 53 | 54 | func (s *store) Read(pos uint64) ([]byte, error) { 55 | s.mu.Lock() 56 | defer s.mu.Unlock() 57 | if err := s.buf.Flush(); err != nil { 58 | return nil, err 59 | } 60 | size := make([]byte, lenWidth) 61 | if _, err := s.File.ReadAt(size, int64(pos)); err != nil { 62 | return nil, err 63 | } 64 | b := make([]byte, enc.Uint64(size)) 65 | if _, err := s.File.ReadAt(b, int64(pos+lenWidth)); err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | func (s *store) ReadAt(p []byte, off int64) (int, error) { 72 | s.mu.Lock() 73 | defer s.mu.Unlock() 74 | if err := s.buf.Flush(); err != nil { 75 | return 0, err 76 | } 77 | return s.File.ReadAt(p, off) 78 | } 79 | 80 | func (s *store) Close() error { 81 | s.mu.Lock() 82 | defer s.mu.Unlock() 83 | if err := s.buf.Flush(); err != nil { 84 | return err 85 | } 86 | return s.File.Close() 87 | } 88 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/metacontroller/templates/metacontroller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: compositecontrollers.metacontroller.k8s.io 5 | spec: 6 | group: metacontroller.k8s.io 7 | version: v1alpha1 8 | scope: Cluster 9 | names: 10 | plural: compositecontrollers 11 | singular: compositecontroller 12 | kind: CompositeController 13 | shortNames: 14 | - cc 15 | - cctl 16 | --- 17 | apiVersion: apiextensions.k8s.io/v1beta1 18 | kind: CustomResourceDefinition 19 | metadata: 20 | name: decoratorcontrollers.metacontroller.k8s.io 21 | spec: 22 | group: metacontroller.k8s.io 23 | version: v1alpha1 24 | scope: Cluster 25 | names: 26 | plural: decoratorcontrollers 27 | singular: decoratorcontroller 28 | kind: DecoratorController 29 | shortNames: 30 | - dec 31 | - decorators 32 | --- 33 | apiVersion: apiextensions.k8s.io/v1beta1 34 | kind: CustomResourceDefinition 35 | metadata: 36 | name: controllerrevisions.metacontroller.k8s.io 37 | spec: 38 | group: metacontroller.k8s.io 39 | version: v1alpha1 40 | scope: Namespaced 41 | names: 42 | plural: controllerrevisions 43 | singular: controllerrevision 44 | kind: ControllerRevision 45 | --- 46 | apiVersion: apps/v1 47 | kind: StatefulSet 48 | metadata: 49 | labels: 50 | app.kubernetes.io/name: metacontroller 51 | name: metacontroller 52 | namespace: metacontroller 53 | spec: 54 | replicas: 1 55 | selector: 56 | matchLabels: 57 | app.kubernetes.io/name: metacontroller 58 | serviceName: "" 59 | template: 60 | metadata: 61 | labels: 62 | app.kubernetes.io/name: metacontroller 63 | spec: 64 | serviceAccountName: metacontroller 65 | containers: 66 | - name: metacontroller 67 | image: metacontroller/metacontroller:v0.4.0 68 | command: ["/usr/bin/metacontroller"] 69 | args: 70 | - --logtostderr 71 | - -v=4 72 | - --discovery-interval=20s 73 | volumeClaimTemplates: [] 74 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/proglog/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "proglog.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "proglog.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "proglog.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "proglog.labels" -}} 37 | helm.sh/chart: {{ include "proglog.chart" . }} 38 | {{ include "proglog.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "proglog.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "proglog.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "proglog.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "proglog.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /ch10-DeployLocally/deploy/proglog/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "proglog.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "proglog.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "proglog.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "proglog.labels" -}} 37 | helm.sh/chart: {{ include "proglog.chart" . }} 38 | {{ include "proglog.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "proglog.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "proglog.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "proglog.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "proglog.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /ch11-DeployCloud/deploy/metacontroller/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "metacontroller.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "metacontroller.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "metacontroller.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "metacontroller.labels" -}} 37 | helm.sh/chart: {{ include "metacontroller.chart" . }} 38 | {{ include "metacontroller.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "metacontroller.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "metacontroller.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "metacontroller.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "metacontroller.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/discovery/membership_test.go: -------------------------------------------------------------------------------- 1 | package discovery_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/hashicorp/serf/serf" 9 | . "github.com/nicewook/proglog/internal/discovery" 10 | "github.com/stretchr/testify/require" 11 | "github.com/travisjeffery/go-dynaport" 12 | ) 13 | 14 | func TestMembership(t *testing.T) { 15 | m, handler := setupMember(t, nil) 16 | m, _ = setupMember(t, m) 17 | m, _ = setupMember(t, m) 18 | 19 | require.Eventually(t, func() bool { 20 | return 2 == len(handler.joins) && 21 | 3 == len(m[0].Members()) && 22 | 0 == len(handler.leaves) 23 | }, 3*time.Second, 250*time.Millisecond) 24 | 25 | require.NoError(t, m[2].Leave()) 26 | 27 | require.Eventually(t, func() bool { 28 | return 2 == len(handler.joins) && 29 | 3 == len(m[0].Members()) && 30 | serf.StatusLeft == m[0].Members()[2].Status && 31 | 1 == len(handler.leaves) 32 | }, 3*time.Second, 250*time.Millisecond) 33 | 34 | require.Equal(t, fmt.Sprintf("%d", 2), <-handler.leaves) 35 | } 36 | 37 | func setupMember(t *testing.T, members []*Membership) ( 38 | []*Membership, *handler, 39 | ) { 40 | id := len(members) 41 | ports := dynaport.Get(1) 42 | addr := fmt.Sprintf("%s:%d", "127.0.0.1", ports[0]) 43 | tags := map[string]string{ 44 | "rpc_addr": addr, 45 | } 46 | c := Config{ 47 | NodeName: fmt.Sprintf("%d", id), 48 | BindAddr: addr, 49 | Tags: tags, 50 | } 51 | h := &handler{} 52 | if len(members) == 0 { 53 | h.joins = make(chan map[string]string, 3) 54 | h.leaves = make(chan string, 3) 55 | } else { 56 | c.StartJoinAddrs = []string{ 57 | members[0].BindAddr, 58 | } 59 | } 60 | m, err := New(h, c) 61 | require.NoError(t, err) 62 | members = append(members, m) 63 | return members, h 64 | } 65 | 66 | type handler struct { 67 | joins chan map[string]string 68 | leaves chan string 69 | } 70 | 71 | func (h *handler) Join(id, addr string) error { 72 | if h.joins != nil { 73 | h.joins <- map[string]string{ 74 | "id": id, 75 | "addr": addr, 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | func (h *handler) Leave(id string) error { 82 | if h.leaves != nil { 83 | h.leaves <- id 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/discovery/membership_test.go: -------------------------------------------------------------------------------- 1 | package discovery_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/hashicorp/serf/serf" 9 | . "github.com/nicewook/proglog/internal/discovery" 10 | "github.com/stretchr/testify/require" 11 | "github.com/travisjeffery/go-dynaport" 12 | ) 13 | 14 | func TestMembership(t *testing.T) { 15 | m, handler := setupMember(t, nil) 16 | m, _ = setupMember(t, m) 17 | m, _ = setupMember(t, m) 18 | 19 | require.Eventually(t, func() bool { 20 | return 2 == len(handler.joins) && 21 | 3 == len(m[0].Members()) && 22 | 0 == len(handler.leaves) 23 | }, 3*time.Second, 250*time.Millisecond) 24 | 25 | require.NoError(t, m[2].Leave()) 26 | 27 | require.Eventually(t, func() bool { 28 | return 2 == len(handler.joins) && 29 | 3 == len(m[0].Members()) && 30 | serf.StatusLeft == m[0].Members()[2].Status && 31 | 1 == len(handler.leaves) 32 | }, 3*time.Second, 250*time.Millisecond) 33 | 34 | require.Equal(t, fmt.Sprintf("%d", 2), <-handler.leaves) 35 | } 36 | 37 | func setupMember(t *testing.T, members []*Membership) ( 38 | []*Membership, *handler, 39 | ) { 40 | id := len(members) 41 | ports := dynaport.Get(1) 42 | addr := fmt.Sprintf("%s:%d", "127.0.0.1", ports[0]) 43 | tags := map[string]string{ 44 | "rpc_addr": addr, 45 | } 46 | c := Config{ 47 | NodeName: fmt.Sprintf("%d", id), 48 | BindAddr: addr, 49 | Tags: tags, 50 | } 51 | h := &handler{} 52 | if len(members) == 0 { 53 | h.joins = make(chan map[string]string, 3) 54 | h.leaves = make(chan string, 3) 55 | } else { 56 | c.StartJoinAddrs = []string{ 57 | members[0].BindAddr, 58 | } 59 | } 60 | m, err := New(h, c) 61 | require.NoError(t, err) 62 | members = append(members, m) 63 | return members, h 64 | } 65 | 66 | type handler struct { 67 | joins chan map[string]string 68 | leaves chan string 69 | } 70 | 71 | func (h *handler) Join(id, addr string) error { 72 | if h.joins != nil { 73 | h.joins <- map[string]string{ 74 | "id": id, 75 | "addr": addr, 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | func (h *handler) Leave(id string) error { 82 | if h.leaves != nil { 83 | h.leaves <- id 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/discovery/membership_test.go: -------------------------------------------------------------------------------- 1 | package discovery_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/hashicorp/serf/serf" 9 | . "github.com/nicewook/proglog/internal/discovery" 10 | "github.com/stretchr/testify/require" 11 | "github.com/travisjeffery/go-dynaport" 12 | ) 13 | 14 | func TestMembership(t *testing.T) { 15 | m, handler := setupMember(t, nil) 16 | m, _ = setupMember(t, m) 17 | m, _ = setupMember(t, m) 18 | 19 | require.Eventually(t, func() bool { 20 | return 2 == len(handler.joins) && 21 | 3 == len(m[0].Members()) && 22 | 0 == len(handler.leaves) 23 | }, 3*time.Second, 250*time.Millisecond) 24 | 25 | require.NoError(t, m[2].Leave()) 26 | 27 | require.Eventually(t, func() bool { 28 | return 2 == len(handler.joins) && 29 | 3 == len(m[0].Members()) && 30 | serf.StatusLeft == m[0].Members()[2].Status && 31 | 1 == len(handler.leaves) 32 | }, 3*time.Second, 250*time.Millisecond) 33 | 34 | require.Equal(t, fmt.Sprintf("%d", 2), <-handler.leaves) 35 | } 36 | 37 | func setupMember(t *testing.T, members []*Membership) ( 38 | []*Membership, *handler, 39 | ) { 40 | id := len(members) 41 | ports := dynaport.Get(1) 42 | addr := fmt.Sprintf("%s:%d", "127.0.0.1", ports[0]) 43 | tags := map[string]string{ 44 | "rpc_addr": addr, 45 | } 46 | c := Config{ 47 | NodeName: fmt.Sprintf("%d", id), 48 | BindAddr: addr, 49 | Tags: tags, 50 | } 51 | h := &handler{} 52 | if len(members) == 0 { 53 | h.joins = make(chan map[string]string, 3) 54 | h.leaves = make(chan string, 3) 55 | } else { 56 | c.StartJoinAddrs = []string{ 57 | members[0].BindAddr, 58 | } 59 | } 60 | m, err := New(h, c) 61 | require.NoError(t, err) 62 | members = append(members, m) 63 | return members, h 64 | } 65 | 66 | type handler struct { 67 | joins chan map[string]string 68 | leaves chan string 69 | } 70 | 71 | func (h *handler) Join(id, addr string) error { 72 | if h.joins != nil { 73 | h.joins <- map[string]string{ 74 | "id": id, 75 | "addr": addr, 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | func (h *handler) Leave(id string) error { 82 | if h.leaves != nil { 83 | h.leaves <- id 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/discovery/membership_test.go: -------------------------------------------------------------------------------- 1 | package discovery_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/hashicorp/serf/serf" 9 | . "github.com/nicewook/proglog/internal/discovery" 10 | "github.com/stretchr/testify/require" 11 | "github.com/travisjeffery/go-dynaport" 12 | ) 13 | 14 | func TestMembership(t *testing.T) { 15 | m, handler := setupMember(t, nil) 16 | m, _ = setupMember(t, m) 17 | m, _ = setupMember(t, m) 18 | 19 | require.Eventually(t, func() bool { 20 | return 2 == len(handler.joins) && 21 | 3 == len(m[0].Members()) && 22 | 0 == len(handler.leaves) 23 | }, 3*time.Second, 250*time.Millisecond) 24 | 25 | require.NoError(t, m[2].Leave()) 26 | 27 | require.Eventually(t, func() bool { 28 | return 2 == len(handler.joins) && 29 | 3 == len(m[0].Members()) && 30 | serf.StatusLeft == m[0].Members()[2].Status && 31 | 1 == len(handler.leaves) 32 | }, 3*time.Second, 250*time.Millisecond) 33 | 34 | require.Equal(t, fmt.Sprintf("%d", 2), <-handler.leaves) 35 | } 36 | 37 | func setupMember(t *testing.T, members []*Membership) ( 38 | []*Membership, *handler, 39 | ) { 40 | id := len(members) 41 | ports := dynaport.Get(1) 42 | addr := fmt.Sprintf("%s:%d", "127.0.0.1", ports[0]) 43 | tags := map[string]string{ 44 | "rpc_addr": addr, 45 | } 46 | c := Config{ 47 | NodeName: fmt.Sprintf("%d", id), 48 | BindAddr: addr, 49 | Tags: tags, 50 | } 51 | h := &handler{} 52 | if len(members) == 0 { 53 | h.joins = make(chan map[string]string, 3) 54 | h.leaves = make(chan string, 3) 55 | } else { 56 | c.StartJoinAddrs = []string{ 57 | members[0].BindAddr, 58 | } 59 | } 60 | m, err := New(h, c) 61 | require.NoError(t, err) 62 | members = append(members, m) 63 | return members, h 64 | } 65 | 66 | type handler struct { 67 | joins chan map[string]string 68 | leaves chan string 69 | } 70 | 71 | func (h *handler) Join(id, addr string) error { 72 | if h.joins != nil { 73 | h.joins <- map[string]string{ 74 | "id": id, 75 | "addr": addr, 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | func (h *handler) Leave(id string) error { 82 | if h.leaves != nil { 83 | h.leaves <- id 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/discovery/membership_test.go: -------------------------------------------------------------------------------- 1 | package discovery_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/hashicorp/serf/serf" 9 | . "github.com/nicewook/proglog/internal/discovery" 10 | "github.com/stretchr/testify/require" 11 | "github.com/travisjeffery/go-dynaport" 12 | ) 13 | 14 | func TestMembership(t *testing.T) { 15 | m, handler := setupMember(t, nil) 16 | m, _ = setupMember(t, m) 17 | m, _ = setupMember(t, m) 18 | 19 | require.Eventually(t, func() bool { 20 | return 2 == len(handler.joins) && 21 | 3 == len(m[0].Members()) && 22 | 0 == len(handler.leaves) 23 | }, 3*time.Second, 250*time.Millisecond) 24 | 25 | require.NoError(t, m[2].Leave()) 26 | 27 | require.Eventually(t, func() bool { 28 | return 2 == len(handler.joins) && 29 | 3 == len(m[0].Members()) && 30 | serf.StatusLeft == m[0].Members()[2].Status && 31 | 1 == len(handler.leaves) 32 | }, 3*time.Second, 250*time.Millisecond) 33 | 34 | require.Equal(t, fmt.Sprintf("%d", 2), <-handler.leaves) 35 | } 36 | 37 | func setupMember(t *testing.T, members []*Membership) ( 38 | []*Membership, *handler, 39 | ) { 40 | id := len(members) 41 | ports := dynaport.Get(1) 42 | addr := fmt.Sprintf("%s:%d", "127.0.0.1", ports[0]) 43 | tags := map[string]string{ 44 | "rpc_addr": addr, 45 | } 46 | c := Config{ 47 | NodeName: fmt.Sprintf("%d", id), 48 | BindAddr: addr, 49 | Tags: tags, 50 | } 51 | h := &handler{} 52 | if len(members) == 0 { 53 | h.joins = make(chan map[string]string, 3) 54 | h.leaves = make(chan string, 3) 55 | } else { 56 | c.StartJoinAddrs = []string{ 57 | members[0].BindAddr, 58 | } 59 | } 60 | m, err := New(h, c) 61 | require.NoError(t, err) 62 | members = append(members, m) 63 | return members, h 64 | } 65 | 66 | type handler struct { 67 | joins chan map[string]string 68 | leaves chan string 69 | } 70 | 71 | func (h *handler) Join(id, addr string) error { 72 | if h.joins != nil { 73 | h.joins <- map[string]string{ 74 | "id": id, 75 | "addr": addr, 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | func (h *handler) Leave(id string) error { 82 | if h.leaves != nil { 83 | h.leaves <- id 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/casbin/casbin v1.9.1 7 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 8 | github.com/hashicorp/serf v0.9.8 9 | github.com/stretchr/testify v1.7.1 10 | github.com/travisjeffery/go-dynaport v1.0.0 11 | github.com/tysonmote/gommap v0.0.2 12 | go.opencensus.io v0.23.0 13 | go.uber.org/zap v1.10.0 14 | google.golang.org/genproto v0.0.0-20220623142657-077d458a5694 15 | google.golang.org/grpc v1.47.0 16 | google.golang.org/protobuf v1.28.0 17 | ) 18 | 19 | require ( 20 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect 21 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect 22 | github.com/davecgh/go-spew v1.1.1 // indirect 23 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect 24 | github.com/golang/protobuf v1.5.2 // indirect 25 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect 26 | github.com/hashicorp/errwrap v1.0.0 // indirect 27 | github.com/hashicorp/go-hclog v0.9.1 // indirect 28 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 29 | github.com/hashicorp/go-msgpack v0.5.5 // indirect 30 | github.com/hashicorp/go-multierror v1.1.0 // indirect 31 | github.com/hashicorp/go-sockaddr v1.0.0 // indirect 32 | github.com/hashicorp/golang-lru v0.5.0 // indirect 33 | github.com/hashicorp/memberlist v0.3.0 // indirect 34 | github.com/hashicorp/raft v1.1.0 // indirect 35 | github.com/hashicorp/raft-boltdb v0.0.0-20220329195025-15018e9b97e0 // indirect 36 | github.com/miekg/dns v1.1.41 // indirect 37 | github.com/pmezard/go-difflib v1.0.0 // indirect 38 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect 39 | github.com/soheilhy/cmux v0.1.5 // indirect 40 | go.etcd.io/bbolt v1.3.5 // indirect 41 | go.uber.org/atomic v1.4.0 // indirect 42 | go.uber.org/multierr v1.1.0 // indirect 43 | golang.org/x/net v0.0.0-20220725212005-46097bf591d3 // indirect 44 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 45 | golang.org/x/text v0.3.7 // indirect 46 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 47 | ) 48 | 49 | replace github.com/hashicorp/raft-boltdb => github.com/travisjeffery/raft-boltdb v1.0.0 50 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nicewook/proglog 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/casbin/casbin v1.9.1 7 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 8 | github.com/hashicorp/serf v0.9.8 9 | github.com/stretchr/testify v1.7.1 10 | github.com/travisjeffery/go-dynaport v1.0.0 11 | github.com/tysonmote/gommap v0.0.2 12 | go.opencensus.io v0.23.0 13 | go.uber.org/zap v1.10.0 14 | google.golang.org/genproto v0.0.0-20220623142657-077d458a5694 15 | google.golang.org/grpc v1.47.0 16 | google.golang.org/protobuf v1.28.0 17 | ) 18 | 19 | require ( 20 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect 21 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect 22 | github.com/davecgh/go-spew v1.1.1 // indirect 23 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect 24 | github.com/golang/protobuf v1.5.2 // indirect 25 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect 26 | github.com/hashicorp/errwrap v1.0.0 // indirect 27 | github.com/hashicorp/go-hclog v0.9.1 // indirect 28 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 29 | github.com/hashicorp/go-msgpack v0.5.5 // indirect 30 | github.com/hashicorp/go-multierror v1.1.0 // indirect 31 | github.com/hashicorp/go-sockaddr v1.0.0 // indirect 32 | github.com/hashicorp/golang-lru v0.5.0 // indirect 33 | github.com/hashicorp/memberlist v0.3.0 // indirect 34 | github.com/hashicorp/raft v1.1.0 // indirect 35 | github.com/hashicorp/raft-boltdb v0.0.0-20220329195025-15018e9b97e0 // indirect 36 | github.com/miekg/dns v1.1.41 // indirect 37 | github.com/pmezard/go-difflib v1.0.0 // indirect 38 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect 39 | github.com/soheilhy/cmux v0.1.5 // indirect 40 | go.etcd.io/bbolt v1.3.5 // indirect 41 | go.uber.org/atomic v1.4.0 // indirect 42 | go.uber.org/multierr v1.1.0 // indirect 43 | golang.org/x/net v0.0.0-20220725212005-46097bf591d3 // indirect 44 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 45 | golang.org/x/text v0.3.7 // indirect 46 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 47 | ) 48 | 49 | replace github.com/hashicorp/raft-boltdb => github.com/travisjeffery/raft-boltdb v1.0.0 50 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/loadbalance/picker_test.go: -------------------------------------------------------------------------------- 1 | package loadbalance_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "google.golang.org/grpc/attributes" 7 | "google.golang.org/grpc/balancer" 8 | "google.golang.org/grpc/balancer/base" 9 | "google.golang.org/grpc/resolver" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/nicewook/proglog/internal/loadbalance" 14 | ) 15 | 16 | func TestPickerNoSubConnAvailable(t *testing.T) { 17 | picker := &loadbalance.Picker{} 18 | for _, method := range []string{ 19 | "/log.vX.Log/Produce", 20 | "/log.vX.Log/Consume", 21 | } { 22 | info := balancer.PickInfo{ 23 | FullMethodName: method, 24 | } 25 | result, err := picker.Pick(info) 26 | require.Equal(t, balancer.ErrNoSubConnAvailable, err) 27 | require.Nil(t, result.SubConn) 28 | } 29 | } 30 | 31 | func TestPickerProducesToLeader(t *testing.T) { 32 | picker, subConns := setupTest() 33 | info := balancer.PickInfo{ 34 | FullMethodName: "/log.vX.Log/Produce", 35 | } 36 | for i := 0; i < 5; i++ { 37 | gotPick, err := picker.Pick(info) 38 | require.NoError(t, err) 39 | require.Equal(t, subConns[0], gotPick.SubConn) 40 | } 41 | } 42 | 43 | func TestPickerConsumesFromFollowers(t *testing.T) { 44 | picker, subConns := setupTest() 45 | info := balancer.PickInfo{ 46 | FullMethodName: "/log.vX.Log/Consume", 47 | } 48 | for i := 0; i < 5; i++ { 49 | pick, err := picker.Pick(info) 50 | require.NoError(t, err) 51 | require.Equal(t, subConns[i%2+1], pick.SubConn) 52 | } 53 | } 54 | 55 | func setupTest() (*loadbalance.Picker, []*subConn) { 56 | var subConns []*subConn 57 | buildInfo := base.PickerBuildInfo{ 58 | ReadySCs: make(map[balancer.SubConn]base.SubConnInfo), 59 | } 60 | for i := 0; i < 3; i++ { 61 | sc := &subConn{} 62 | addr := resolver.Address{ 63 | Attributes: attributes.New("is_leader", i == 0), 64 | } 65 | // 0th sub conn is the leader 66 | sc.UpdateAddresses([]resolver.Address{addr}) 67 | buildInfo.ReadySCs[sc] = base.SubConnInfo{Address: addr} 68 | subConns = append(subConns, sc) 69 | } 70 | picker := &loadbalance.Picker{} 71 | picker.Build(buildInfo) 72 | return picker, subConns 73 | } 74 | 75 | // subConn implements balancer.SubConn. 76 | type subConn struct { 77 | addrs []resolver.Address 78 | } 79 | 80 | func (s *subConn) UpdateAddresses(addrs []resolver.Address) { 81 | s.addrs = addrs 82 | } 83 | 84 | func (s *subConn) Connect() {} 85 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/loadbalance/picker_test.go: -------------------------------------------------------------------------------- 1 | package loadbalance_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "google.golang.org/grpc/attributes" 7 | "google.golang.org/grpc/balancer" 8 | "google.golang.org/grpc/balancer/base" 9 | "google.golang.org/grpc/resolver" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/nicewook/proglog/internal/loadbalance" 14 | ) 15 | 16 | func TestPickerNoSubConnAvailable(t *testing.T) { 17 | picker := &loadbalance.Picker{} 18 | for _, method := range []string{ 19 | "/log.vX.Log/Produce", 20 | "/log.vX.Log/Consume", 21 | } { 22 | info := balancer.PickInfo{ 23 | FullMethodName: method, 24 | } 25 | result, err := picker.Pick(info) 26 | require.Equal(t, balancer.ErrNoSubConnAvailable, err) 27 | require.Nil(t, result.SubConn) 28 | } 29 | } 30 | 31 | func TestPickerProducesToLeader(t *testing.T) { 32 | picker, subConns := setupTest() 33 | info := balancer.PickInfo{ 34 | FullMethodName: "/log.vX.Log/Produce", 35 | } 36 | for i := 0; i < 5; i++ { 37 | gotPick, err := picker.Pick(info) 38 | require.NoError(t, err) 39 | require.Equal(t, subConns[0], gotPick.SubConn) 40 | } 41 | } 42 | 43 | func TestPickerConsumesFromFollowers(t *testing.T) { 44 | picker, subConns := setupTest() 45 | info := balancer.PickInfo{ 46 | FullMethodName: "/log.vX.Log/Consume", 47 | } 48 | for i := 0; i < 5; i++ { 49 | pick, err := picker.Pick(info) 50 | require.NoError(t, err) 51 | require.Equal(t, subConns[i%2+1], pick.SubConn) 52 | } 53 | } 54 | 55 | func setupTest() (*loadbalance.Picker, []*subConn) { 56 | var subConns []*subConn 57 | buildInfo := base.PickerBuildInfo{ 58 | ReadySCs: make(map[balancer.SubConn]base.SubConnInfo), 59 | } 60 | for i := 0; i < 3; i++ { 61 | sc := &subConn{} 62 | addr := resolver.Address{ 63 | Attributes: attributes.New("is_leader", i == 0), 64 | } 65 | // 0th sub conn is the leader 66 | sc.UpdateAddresses([]resolver.Address{addr}) 67 | buildInfo.ReadySCs[sc] = base.SubConnInfo{Address: addr} 68 | subConns = append(subConns, sc) 69 | } 70 | picker := &loadbalance.Picker{} 71 | picker.Build(buildInfo) 72 | return picker, subConns 73 | } 74 | 75 | // subConn implements balancer.SubConn. 76 | type subConn struct { 77 | addrs []resolver.Address 78 | } 79 | 80 | func (s *subConn) UpdateAddresses(addrs []resolver.Address) { 81 | s.addrs = addrs 82 | } 83 | 84 | func (s *subConn) Connect() {} 85 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/loadbalance/picker_test.go: -------------------------------------------------------------------------------- 1 | package loadbalance_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "google.golang.org/grpc/attributes" 7 | "google.golang.org/grpc/balancer" 8 | "google.golang.org/grpc/balancer/base" 9 | "google.golang.org/grpc/resolver" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/nicewook/proglog/internal/loadbalance" 14 | ) 15 | 16 | func TestPickerNoSubConnAvailable(t *testing.T) { 17 | picker := &loadbalance.Picker{} 18 | for _, method := range []string{ 19 | "/log.vX.Log/Produce", 20 | "/log.vX.Log/Consume", 21 | } { 22 | info := balancer.PickInfo{ 23 | FullMethodName: method, 24 | } 25 | result, err := picker.Pick(info) 26 | require.Equal(t, balancer.ErrNoSubConnAvailable, err) 27 | require.Nil(t, result.SubConn) 28 | } 29 | } 30 | 31 | func TestPickerProducesToLeader(t *testing.T) { 32 | picker, subConns := setupTest() 33 | info := balancer.PickInfo{ 34 | FullMethodName: "/log.vX.Log/Produce", 35 | } 36 | for i := 0; i < 5; i++ { 37 | gotPick, err := picker.Pick(info) 38 | require.NoError(t, err) 39 | require.Equal(t, subConns[0], gotPick.SubConn) 40 | } 41 | } 42 | 43 | func TestPickerConsumesFromFollowers(t *testing.T) { 44 | picker, subConns := setupTest() 45 | info := balancer.PickInfo{ 46 | FullMethodName: "/log.vX.Log/Consume", 47 | } 48 | for i := 0; i < 5; i++ { 49 | pick, err := picker.Pick(info) 50 | require.NoError(t, err) 51 | require.Equal(t, subConns[i%2+1], pick.SubConn) 52 | } 53 | } 54 | 55 | func setupTest() (*loadbalance.Picker, []*subConn) { 56 | var subConns []*subConn 57 | buildInfo := base.PickerBuildInfo{ 58 | ReadySCs: make(map[balancer.SubConn]base.SubConnInfo), 59 | } 60 | for i := 0; i < 3; i++ { 61 | sc := &subConn{} 62 | addr := resolver.Address{ 63 | Attributes: attributes.New("is_leader", i == 0), 64 | } 65 | // 0th sub conn is the leader 66 | sc.UpdateAddresses([]resolver.Address{addr}) 67 | buildInfo.ReadySCs[sc] = base.SubConnInfo{Address: addr} 68 | subConns = append(subConns, sc) 69 | } 70 | picker := &loadbalance.Picker{} 71 | picker.Build(buildInfo) 72 | return picker, subConns 73 | } 74 | 75 | // subConn implements balancer.SubConn. 76 | type subConn struct { 77 | addrs []resolver.Address 78 | } 79 | 80 | func (s *subConn) UpdateAddresses(addrs []resolver.Address) { 81 | s.addrs = addrs 82 | } 83 | 84 | func (s *subConn) Connect() {} 85 | -------------------------------------------------------------------------------- /ch11-DeployCloud/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch10-DeployLocally/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch3-WriteALogPacakge/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch5-SecureYourServices/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch6-ObserveYourServices/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch7-ServerSideServiceDiscovery/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch8-CoordinateWithConsensus/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch9-ClientSideServiceDiscovery/internal/log/index.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/tysonmote/gommap" 8 | ) 9 | 10 | // 인덱스 항목 내의 바이트 개수 11 | var ( 12 | offWidth uint64 = 4 // 레코드의 오프셋 정보 uint32 4바이트 - 즉 몇 번째인지 13 | posWidth uint64 = 8 // 위치(position) 정보 uint64 8바이트 - 즉 정확한 위치 14 | entWidth = offWidth + posWidth 15 | ) 16 | 17 | type index struct { 18 | file *os.File 19 | mmap gommap.MMap 20 | size uint64 21 | } 22 | 23 | func newIndex(f *os.File, c Config) (*index, error) { 24 | idx := &index{ 25 | file: f, 26 | } 27 | fi, err := os.Stat(f.Name()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | idx.size = uint64(fi.Size()) // 현재 사이즈 저장 32 | // 일단 최대 사이즈로 Truncate() 해줘서 mmap 대비 33 | if err = os.Truncate(f.Name(), int64(c.Segment.MaxIndexBytes)); err != nil { 34 | return nil, err 35 | } 36 | if idx.mmap, err = gommap.Map(idx.file.Fd(), 37 | gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED); err != nil { 38 | return nil, err 39 | } 40 | return idx, nil 41 | } 42 | 43 | func (i *index) Close() error { 44 | // 메모리 맵 파일부터 싱크 45 | if err := i.mmap.Sync(gommap.MS_SYNC); err != nil { 46 | return err 47 | } 48 | // 그 다음 파일 싱크 49 | if err := i.file.Sync(); err != nil { 50 | return err 51 | } 52 | // 이제 실제 크기만큼 다시 자르기 53 | if err := i.file.Truncate(int64(i.size)); err != nil { 54 | return err 55 | } 56 | return i.file.Close() 57 | } 58 | 59 | // in 번째 인덱스를 읽어서 60 | // 앞에 4바이트는 out, 그 다음 8바이트는 pos 정보로 파싱해서 리턴하가. 61 | func (i *index) Read(in int64) (out uint32, pos uint64, err error) { 62 | if i.size == 0 { 63 | return 0, 0, io.EOF 64 | } 65 | if in == -1 { 66 | out = uint32((i.size / entWidth) - 1) // 가장 마지막 인덱스 계산 67 | } else { 68 | out = uint32(in) 69 | } 70 | pos = uint64(out) * entWidth // 몇 번째 바이트를 읽을 지 계산 71 | if i.size < pos+entWidth { 72 | return 0, 0, io.EOF 73 | } 74 | // out과 pos를 여러번 재활용해서 좀 헷갈린다. 75 | out = enc.Uint32(i.mmap[pos : pos+offWidth]) // 4바이트 읽기 76 | pos = enc.Uint64(i.mmap[pos+offWidth : pos+entWidth]) // 8바이트 읽기 77 | return out, pos, nil 78 | } 79 | 80 | func (i *index) Write(off uint32, pos uint64) error { 81 | if uint64(len(i.mmap)) < i.size+entWidth { // 인덱스 하나 추가해도 크기 괜찮은가? 82 | return io.EOF 83 | } 84 | enc.PutUint32(i.mmap[i.size:i.size+offWidth], off) 85 | enc.PutUint64(i.mmap[i.size+offWidth:i.size+entWidth], pos) 86 | i.size += uint64(entWidth) 87 | return nil 88 | } 89 | 90 | func (i *index) Name() string { 91 | return i.file.Name() 92 | } 93 | -------------------------------------------------------------------------------- /ch4-ServerRequestWithgRPC/internal/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | api "github.com/nicewook/proglog/api/v1" 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | type CommitLog interface { 11 | Append(*api.Record) (uint64, error) 12 | Read(uint64) (*api.Record, error) 13 | } 14 | 15 | type Config struct { 16 | CommitLog 17 | } 18 | 19 | var _ api.LogServer = (*grpcServer)(nil) 20 | 21 | type grpcServer struct { 22 | api.UnimplementedLogServer 23 | *Config 24 | } 25 | 26 | func newgrpcServer(config *Config) (srv *grpcServer, err error) { 27 | srv = &grpcServer{ 28 | Config: config, 29 | } 30 | return srv, nil 31 | } 32 | 33 | func NewGRPCServer(config *Config) (*grpc.Server, error) { 34 | gsrv := grpc.NewServer() 35 | srv, err := newgrpcServer(config) 36 | if err != nil { 37 | return nil, err 38 | } 39 | api.RegisterLogServer(gsrv, srv) 40 | return gsrv, nil 41 | } 42 | 43 | func (s *grpcServer) Produce(ctx context.Context, req *api.ProduceRequest) ( 44 | *api.ProduceResponse, error) { 45 | offset, err := s.CommitLog.Append(req.Record) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return &api.ProduceResponse{Offset: offset}, nil 50 | } 51 | 52 | func (s *grpcServer) Consume(ctx context.Context, req *api.ConsumeRequest) ( 53 | *api.ConsumeResponse, error) { 54 | record, err := s.CommitLog.Read(req.Offset) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return &api.ConsumeResponse{Record: record}, nil 59 | } 60 | func (s *grpcServer) ProduceStream( 61 | stream api.Log_ProduceStreamServer, 62 | ) error { 63 | for { 64 | req, err := stream.Recv() 65 | if err != nil { 66 | return err 67 | } 68 | res, err := s.Produce(stream.Context(), req) 69 | if err != nil { 70 | return err 71 | } 72 | if err = stream.Send(res); err != nil { 73 | return err 74 | } 75 | } 76 | } 77 | 78 | func (s *grpcServer) ConsumeStream( 79 | req *api.ConsumeRequest, 80 | stream api.Log_ConsumeStreamServer, 81 | ) error { 82 | for { 83 | select { 84 | case <-stream.Context().Done(): 85 | return nil 86 | default: 87 | res, err := s.Consume(stream.Context(), req) 88 | switch err.(type) { 89 | case nil: 90 | case api.ErrOffsetOutOfRange: 91 | continue 92 | default: 93 | return err 94 | } 95 | if err = stream.Send(res); err != nil { 96 | return err 97 | } 98 | req.Offset++ 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /ch1-LetsGo/internal/server/http.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | // 최종 서버 11 | // httpsrv에는 실제 서버의 역할보다는 그냥 핸들러들만 몰아 넣어둔 것이다. 12 | func NewHTTPServer(addr string) *http.Server { 13 | httpsrv := newHTTPServer() 14 | r := mux.NewRouter() 15 | r.HandleFunc("/", httpsrv.handleProduce).Methods("POST") 16 | r.HandleFunc("/", httpsrv.handleConsume).Methods("GET") 17 | return &http.Server{ 18 | Addr: addr, 19 | Handler: r, 20 | } 21 | } 22 | 23 | // 우리가 구현한 로그를 담고 있다. 24 | // hewHTTPServer는 실제 작업을 담고 있다. 25 | type httpServer struct { 26 | Log *Log 27 | } 28 | 29 | func newHTTPServer() *httpServer { 30 | return &httpServer{ 31 | Log: NewLog(), 32 | } 33 | } 34 | 35 | // HTTP로 들어오는 요청과 그 응답 구조를 정의해 두었다. 36 | type ProduceRequest struct { 37 | Record Record `json:"record"` 38 | } 39 | 40 | type ProduceResponse struct { 41 | Offset uint64 `json:"offset"` 42 | } 43 | 44 | type ConsumeRequest struct { 45 | Offset uint64 `json:"offset"` 46 | } 47 | 48 | type ConsumeResponse struct { 49 | Record Record `json:"record"` 50 | } 51 | 52 | // handleProduce는 httpServer 내의 로그에 값을 추가한다. 53 | func (s *httpServer) handleProduce(w http.ResponseWriter, r *http.Request) { 54 | var req ProduceRequest 55 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 56 | http.Error(w, err.Error(), http.StatusBadRequest) 57 | return 58 | } 59 | offset, err := s.Log.Append(req.Record) 60 | if err != nil { 61 | http.Error(w, err.Error(), http.StatusInternalServerError) 62 | return 63 | } 64 | res := ProduceResponse{Offset: offset} 65 | if err := json.NewEncoder(w).Encode(res); err != nil { 66 | http.Error(w, err.Error(), http.StatusInternalServerError) 67 | return 68 | } 69 | } 70 | 71 | // handleConsume httpServer 내의 로그의 값을 읽는다. 72 | func (s *httpServer) handleConsume(w http.ResponseWriter, r *http.Request) { 73 | var req ConsumeRequest 74 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 75 | http.Error(w, err.Error(), http.StatusBadRequest) 76 | return 77 | } 78 | record, err := s.Log.Read(req.Offset) 79 | if err != nil { 80 | if err == ErrOffsetNotFound { 81 | http.Error(w, err.Error(), http.StatusNotFound) 82 | return 83 | } 84 | http.Error(w, err.Error(), http.StatusInternalServerError) 85 | return 86 | } 87 | res := ConsumeResponse{Record: record} 88 | if err := json.NewEncoder(w).Encode(res); err != nil { 89 | http.Error(w, err.Error(), http.StatusInternalServerError) 90 | return 91 | } 92 | } 93 | --------------------------------------------------------------------------------