├── README.md ├── ch10 ├── echo │ ├── echo │ ├── go.mod │ ├── go.sum │ ├── Dockerfile │ └── main.go ├── post.json ├── task.json ├── add_task.json ├── task │ ├── main.go │ ├── state_machine.go │ └── task.go ├── stop_task.json ├── task1.json ├── task2.json ├── task3.json ├── utils │ └── retry.go ├── manager │ ├── api.go │ └── handlers.go ├── go.mod ├── worker │ ├── api.go │ └── handlers.go ├── main.go ├── node │ └── node.go └── stats │ └── stats.go ├── ch11 ├── echo │ ├── echo │ ├── go.mod │ ├── go.sum │ ├── Dockerfile │ └── main.go ├── post.json ├── task.json ├── add_task.json ├── task │ ├── main.go │ ├── state_machine.go │ └── task.go ├── stop_task.json ├── task1.json ├── task2.json ├── task3.json ├── utils │ └── retry.go ├── manager │ ├── api.go │ └── handlers.go ├── worker │ ├── api.go │ └── handlers.go ├── go.mod ├── main.go ├── node │ └── node.go └── stats │ └── stats.go ├── ch12 ├── echo │ ├── echo │ ├── go.mod │ ├── go.sum │ ├── Dockerfile │ └── main.go ├── main.go ├── post.json ├── task.json ├── add_task.json ├── task │ ├── main.go │ ├── state_machine.go │ └── task.go ├── stop_task.json ├── task1.json ├── task2.json ├── task3.json ├── utils │ └── retry.go ├── manager │ ├── api.go │ └── handlers.go ├── worker │ ├── api.go │ └── handlers.go ├── cmd │ ├── stop.go │ ├── node.go │ ├── worker.go │ ├── root.go │ ├── manager.go │ ├── status.go │ └── run.go ├── go.mod ├── node │ └── node.go └── stats │ └── stats.go ├── ch9 ├── echo │ ├── go.mod │ ├── go.sum │ ├── Dockerfile │ └── main.go ├── scheduler │ └── scheduler.go ├── add_task.json ├── post.json ├── task.json ├── node │ └── node.go ├── task │ ├── main.go │ └── state_machine.go ├── stop_task.json ├── go.mod ├── manager │ ├── api.go │ └── handlers.go ├── worker │ ├── api.go │ ├── handlers.go │ └── stats.go └── main.go ├── ch2 ├── scheduler │ └── scheduler.go ├── node │ └── node.go ├── worker │ └── worker.go ├── manager │ └── manager.go ├── go.mod ├── task │ └── task.go └── main.go ├── ch3 ├── scheduler │ └── scheduler.go ├── node │ └── node.go ├── task │ ├── main.go │ ├── state_machine.go │ └── task.go ├── go.mod ├── worker │ └── worker.go ├── manager │ └── manager.go └── main.go ├── ch4 ├── scheduler │ └── scheduler.go ├── node │ └── node.go ├── task │ ├── main.go │ ├── state_machine.go │ └── task.go ├── go.mod ├── manager │ └── manager.go ├── main.go └── worker │ └── worker.go ├── ch5 ├── scheduler │ └── scheduler.go ├── node │ └── node.go ├── add_task.json ├── post.json ├── task │ ├── main.go │ ├── state_machine.go │ └── task.go ├── stop_task.json ├── go.mod ├── worker │ ├── api.go │ ├── handlers.go │ └── worker.go ├── manager │ └── manager.go └── main.go ├── ch6 ├── scheduler │ └── scheduler.go ├── add_task.json ├── post.json ├── node │ └── node.go ├── task │ ├── main.go │ ├── state_machine.go │ └── task.go ├── stop_task.json ├── go.mod ├── manager │ └── manager.go ├── worker │ ├── api.go │ ├── handlers.go │ ├── stats.go │ └── worker.go └── main.go ├── ch7 ├── scheduler │ └── scheduler.go ├── add_task.json ├── post.json ├── node │ └── node.go ├── task │ ├── main.go │ └── state_machine.go ├── stop_task.json ├── go.mod ├── worker │ ├── api.go │ ├── handlers.go │ ├── stats.go │ └── worker.go ├── main.go └── manager │ └── manager.go └── ch8 ├── scheduler └── scheduler.go ├── add_task.json ├── post.json ├── task.json ├── node └── node.go ├── task ├── main.go └── state_machine.go ├── stop_task.json ├── go.mod ├── worker ├── api.go ├── handlers.go ├── stats.go └── worker.go ├── manager ├── api.go ├── handlers.go └── manager.go └── main.go /README.md: -------------------------------------------------------------------------------- 1 | Source code for Build an Orchestrator in Go (From Scratch). 2 | -------------------------------------------------------------------------------- /ch10/echo/echo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildorchestratoringo/code/HEAD/ch10/echo/echo -------------------------------------------------------------------------------- /ch11/echo/echo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildorchestratoringo/code/HEAD/ch11/echo/echo -------------------------------------------------------------------------------- /ch12/echo/echo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildorchestratoringo/code/HEAD/ch12/echo/echo -------------------------------------------------------------------------------- /ch12/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "cube/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /ch10/echo/go.mod: -------------------------------------------------------------------------------- 1 | module echo 2 | 3 | go 1.17 4 | 5 | require github.com/go-chi/chi/v5 v5.0.7 // indirect 6 | -------------------------------------------------------------------------------- /ch11/echo/go.mod: -------------------------------------------------------------------------------- 1 | module echo 2 | 3 | go 1.17 4 | 5 | require github.com/go-chi/chi/v5 v5.0.7 // indirect 6 | -------------------------------------------------------------------------------- /ch12/echo/go.mod: -------------------------------------------------------------------------------- 1 | module echo 2 | 3 | go 1.17 4 | 5 | require github.com/go-chi/chi/v5 v5.0.7 // indirect 6 | -------------------------------------------------------------------------------- /ch9/echo/go.mod: -------------------------------------------------------------------------------- 1 | module echo 2 | 3 | go 1.17 4 | 5 | require github.com/go-chi/chi/v5 v5.0.7 // indirect 6 | -------------------------------------------------------------------------------- /ch2/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | type Scheduler interface { 4 | SelectCandidateNodes() 5 | Score() 6 | Pick() 7 | } 8 | -------------------------------------------------------------------------------- /ch3/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | type Scheduler interface { 4 | SelectCandidateNodes() 5 | Score() 6 | Pick() 7 | } 8 | -------------------------------------------------------------------------------- /ch4/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | type Scheduler interface { 4 | SelectCandidateNodes() 5 | Score() 6 | Pick() 7 | } 8 | -------------------------------------------------------------------------------- /ch5/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | type Scheduler interface { 4 | SelectCandidateNodes() 5 | Score() 6 | Pick() 7 | } 8 | -------------------------------------------------------------------------------- /ch6/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | type Scheduler interface { 4 | SelectCandidateNodes() 5 | Score() 6 | Pick() 7 | } 8 | -------------------------------------------------------------------------------- /ch7/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | type Scheduler interface { 4 | SelectCandidateNodes() 5 | Score() 6 | Pick() 7 | } 8 | -------------------------------------------------------------------------------- /ch8/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | type Scheduler interface { 4 | SelectCandidateNodes() 5 | Score() 6 | Pick() 7 | } 8 | -------------------------------------------------------------------------------- /ch9/scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | type Scheduler interface { 4 | SelectCandidateNodes() 5 | Score() 6 | Pick() 7 | } 8 | -------------------------------------------------------------------------------- /ch10/echo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= 2 | github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 3 | -------------------------------------------------------------------------------- /ch11/echo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= 2 | github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 3 | -------------------------------------------------------------------------------- /ch12/echo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= 2 | github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 3 | -------------------------------------------------------------------------------- /ch9/echo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= 2 | github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 3 | -------------------------------------------------------------------------------- /ch10/echo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17.8-alpine3.14 as builder 2 | RUN mkdir /build 3 | ADD *.go go.* /build/ 4 | WORKDIR /build 5 | RUN go build . 6 | 7 | FROM alpine:3.14 8 | RUN mkdir /app 9 | WORKDIR /app 10 | COPY --from=builder /build/echo . 11 | 12 | CMD /app/echo 13 | -------------------------------------------------------------------------------- /ch11/echo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17.8-alpine3.14 as builder 2 | RUN mkdir /build 3 | ADD *.go go.* /build/ 4 | WORKDIR /build 5 | RUN go build . 6 | 7 | FROM alpine:3.14 8 | RUN mkdir /app 9 | WORKDIR /app 10 | COPY --from=builder /build/echo . 11 | 12 | CMD /app/echo 13 | -------------------------------------------------------------------------------- /ch12/echo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17.8-alpine3.14 as builder 2 | RUN mkdir /build 3 | ADD *.go go.* /build/ 4 | WORKDIR /build 5 | RUN go build . 6 | 7 | FROM alpine:3.14 8 | RUN mkdir /app 9 | WORKDIR /app 10 | COPY --from=builder /build/echo . 11 | 12 | CMD /app/echo 13 | -------------------------------------------------------------------------------- /ch9/echo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17.8-alpine3.14 as builder 2 | RUN mkdir /build 3 | ADD *.go go.* /build/ 4 | WORKDIR /build 5 | RUN go build . 6 | 7 | FROM alpine:3.14 8 | RUN mkdir /app 9 | WORKDIR /app 10 | COPY --from=builder /build/echo . 11 | 12 | CMD /app/echo 13 | -------------------------------------------------------------------------------- /ch10/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch10/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-8", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch11/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch11/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-8", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch12/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch12/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-8", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch6/add_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch6/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch7/add_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch7/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch8/add_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch8/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch8/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-8", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch9/add_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch9/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch9/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-8", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch10/add_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch11/add_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch12/add_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ch2/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | type Node struct { 4 | Name string 5 | Ip string 6 | Cores int 7 | Memory int 8 | MemoryAllocated int 9 | Disk int 10 | DiskAllocated int 11 | Role string 12 | TaskCount int 13 | } 14 | -------------------------------------------------------------------------------- /ch3/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | type Node struct { 4 | Name string 5 | Ip string 6 | Cores int 7 | Memory int 8 | MemoryAllocated int 9 | Disk int 10 | DiskAllocated int 11 | Role string 12 | TaskCount int 13 | } 14 | -------------------------------------------------------------------------------- /ch4/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | type Node struct { 4 | Name string 5 | Ip string 6 | Cores int 7 | Memory int 8 | MemoryAllocated int 9 | Disk int 10 | DiskAllocated int 11 | Role string 12 | TaskCount int 13 | } 14 | -------------------------------------------------------------------------------- /ch5/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | type Node struct { 4 | Name string 5 | Ip string 6 | Cores int 7 | Memory int 8 | MemoryAllocated int 9 | Disk int 10 | DiskAllocated int 11 | Role string 12 | TaskCount int 13 | } 14 | -------------------------------------------------------------------------------- /ch6/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | type Node struct { 4 | Name string 5 | Ip string 6 | Cores int 7 | Memory int 8 | MemoryAllocated int 9 | Disk int 10 | DiskAllocated int 11 | Role string 12 | TaskCount int 13 | } 14 | -------------------------------------------------------------------------------- /ch7/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | type Node struct { 4 | Name string 5 | Ip string 6 | Cores int 7 | Memory int 8 | MemoryAllocated int 9 | Disk int 10 | DiskAllocated int 11 | Role string 12 | TaskCount int 13 | } 14 | -------------------------------------------------------------------------------- /ch8/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | type Node struct { 4 | Name string 5 | Ip string 6 | Cores int 7 | Memory int 8 | MemoryAllocated int 9 | Disk int 10 | DiskAllocated int 11 | Role string 12 | TaskCount int 13 | } 14 | -------------------------------------------------------------------------------- /ch9/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | type Node struct { 4 | Name string 5 | Ip string 6 | Cores int 7 | Memory int 8 | MemoryAllocated int 9 | Disk int 10 | DiskAllocated int 11 | Role string 12 | TaskCount int 13 | } 14 | -------------------------------------------------------------------------------- /ch5/add_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-5", 8 | "Image": "strm/helloworld-http", 9 | "Cpu": 1.0 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch5/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Timestamp": "", 5 | "Task": { 6 | "State": 1, 7 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http", 10 | "Cpu": 1.0 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ch10/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch11/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch12/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch3/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch4/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch5/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch6/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch7/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch8/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch9/task/main.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/docker/docker/client" 5 | ) 6 | 7 | func main() { 8 | c := Config{ 9 | Name: "test-container-1", 10 | Image: "postgres:13", 11 | } 12 | 13 | dc, _ := client.NewClientWithOpts(client.FromEnv) 14 | d := Docker{ 15 | Client: dc, 16 | Config: c, 17 | } 18 | 19 | d.Run() 20 | } 21 | -------------------------------------------------------------------------------- /ch10/stop_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 3, 4 | "Task": { 5 | "State": 3, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "ContainerID": "b97526c353d5d9fc9092c32f1e4b5d6ad9c5fa88310c8d7e317663cf4d418e64", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch11/stop_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 3, 4 | "Task": { 5 | "State": 3, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "ContainerID": "b97526c353d5d9fc9092c32f1e4b5d6ad9c5fa88310c8d7e317663cf4d418e64", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch12/stop_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 3, 4 | "Task": { 5 | "State": 3, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "ContainerID": "b97526c353d5d9fc9092c32f1e4b5d6ad9c5fa88310c8d7e317663cf4d418e64", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch5/stop_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 3, 4 | "Task": { 5 | "State": 3, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "ContainerID": "b97526c353d5d9fc9092c32f1e4b5d6ad9c5fa88310c8d7e317663cf4d418e64", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch6/stop_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 3, 4 | "Task": { 5 | "State": 3, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "ContainerID": "b97526c353d5d9fc9092c32f1e4b5d6ad9c5fa88310c8d7e317663cf4d418e64", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch7/stop_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 3, 4 | "Task": { 5 | "State": 3, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "ContainerID": "b97526c353d5d9fc9092c32f1e4b5d6ad9c5fa88310c8d7e317663cf4d418e64", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch8/stop_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 3, 4 | "Task": { 5 | "State": 3, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "ContainerID": "b97526c353d5d9fc9092c32f1e4b5d6ad9c5fa88310c8d7e317663cf4d418e64", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch9/stop_task.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 3, 4 | "Task": { 5 | "State": 3, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "ContainerID": "b97526c353d5d9fc9092c32f1e4b5d6ad9c5fa88310c8d7e317663cf4d418e64", 8 | "Name": "test-chapter-5", 9 | "Image": "strm/helloworld-http" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch10/task1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "a7aa1d44-08f6-443e-9378-f5884311019e", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "bb1d59ef-9fc1-4e4b-a44d-db571eeed203", 7 | "Name": "test-chapter-9.1", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch10/task2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-9.2", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch10/task3.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "3b1c5556-ef0f-4d24-bbda-426029d47dcf", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "95fbe134-7f19-496a-acfc-c7853e5b4cd2", 7 | "Name": "test-chapter-9.3", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch11/task1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "a7aa1d44-08f6-443e-9378-f5884311019e", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "bb1d59ef-9fc1-4e4b-a44d-db571eeed203", 7 | "Name": "test-chapter-9.1", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch11/task2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-9.2", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch11/task3.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "3b1c5556-ef0f-4d24-bbda-426029d47dcf", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "95fbe134-7f19-496a-acfc-c7853e5b4cd2", 7 | "Name": "test-chapter-9.3", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch12/task1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "a7aa1d44-08f6-443e-9378-f5884311019e", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "bb1d59ef-9fc1-4e4b-a44d-db571eeed203", 7 | "Name": "test-chapter-9.1", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch12/task2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "6be4cb6b-61d1-40cb-bc7b-9cacefefa60c", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "21b23589-5d2d-4731-b5c9-a97e9832d021", 7 | "Name": "test-chapter-9.2", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch12/task3.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "3b1c5556-ef0f-4d24-bbda-426029d47dcf", 3 | "State": 2, 4 | "Task": { 5 | "State": 1, 6 | "ID": "95fbe134-7f19-496a-acfc-c7853e5b4cd2", 7 | "Name": "test-chapter-9.3", 8 | "Image": "timboring/echo-server:latest", 9 | "ExposedPorts": { 10 | "7777/tcp": {} 11 | }, 12 | "PortBindings": { 13 | "7777/tcp": "7777" 14 | }, 15 | "HealthCheck": "/health" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ch10/utils/retry.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func HTTPWithRetry(f func(string) (*http.Response, error), url string) (*http.Response, error) { 10 | count := 10 11 | var resp *http.Response 12 | var err error 13 | for i := 0; i < count; i++ { 14 | resp, err = f(url) 15 | if err != nil { 16 | fmt.Printf("Error calling url %v\n", url) 17 | time.Sleep(5 * time.Second) 18 | } else { 19 | break 20 | } 21 | } 22 | return resp, err 23 | } 24 | -------------------------------------------------------------------------------- /ch11/utils/retry.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func HTTPWithRetry(f func(string) (*http.Response, error), url string) (*http.Response, error) { 10 | count := 10 11 | var resp *http.Response 12 | var err error 13 | for i := 0; i < count; i++ { 14 | resp, err = f(url) 15 | if err != nil { 16 | fmt.Printf("Error calling url %v\n", url) 17 | time.Sleep(5 * time.Second) 18 | } else { 19 | break 20 | } 21 | } 22 | return resp, err 23 | } 24 | -------------------------------------------------------------------------------- /ch12/utils/retry.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func HTTPWithRetry(f func(string) (*http.Response, error), url string) (*http.Response, error) { 10 | count := 10 11 | var resp *http.Response 12 | var err error 13 | for i := 0; i < count; i++ { 14 | resp, err = f(url) 15 | if err != nil { 16 | fmt.Printf("Error calling url %v\n", url) 17 | time.Sleep(5 * time.Second) 18 | } else { 19 | break 20 | } 21 | } 22 | return resp, err 23 | } 24 | -------------------------------------------------------------------------------- /ch3/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/containerd/containerd v1.5.1 // indirect 8 | github.com/docker/docker v20.10.6+incompatible // indirect 9 | github.com/docker/go-connections v0.4.0 // indirect 10 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect 11 | github.com/google/uuid v1.2.0 // indirect 12 | github.com/moby/moby v20.10.6+incompatible // indirect 13 | github.com/sirupsen/logrus v1.8.1 // indirect 14 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 15 | google.golang.org/grpc v1.37.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /ch4/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/containerd/containerd v1.5.1 // indirect 8 | github.com/docker/docker v20.10.6+incompatible // indirect 9 | github.com/docker/go-connections v0.4.0 // indirect 10 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect 11 | github.com/google/uuid v1.2.0 // indirect 12 | github.com/moby/moby v20.10.6+incompatible // indirect 13 | github.com/sirupsen/logrus v1.8.1 // indirect 14 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 15 | google.golang.org/grpc v1.37.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /ch5/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/containerd/containerd v1.5.1 // indirect 8 | github.com/docker/docker v20.10.6+incompatible 9 | github.com/docker/go-connections v0.4.0 10 | github.com/go-chi/chi/v5 v5.0.4 11 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 12 | github.com/google/uuid v1.2.0 13 | github.com/moby/moby v20.10.6+incompatible 14 | github.com/morikuni/aec v1.0.0 // indirect 15 | github.com/sirupsen/logrus v1.8.1 // indirect 16 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 17 | google.golang.org/grpc v1.37.1 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /ch2/worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/golang-collections/collections/queue" 7 | "github.com/google/uuid" 8 | 9 | "cube/task" 10 | ) 11 | 12 | type Worker struct { 13 | Name string 14 | Queue queue.Queue 15 | Db map[uuid.UUID]*task.Task 16 | TaskCount int 17 | } 18 | 19 | func (w *Worker) CollectStats() { 20 | fmt.Println("I will collect stats") 21 | } 22 | 23 | func (w *Worker) RunTask() { 24 | fmt.Println("I will start or stop a task") 25 | } 26 | 27 | func (w *Worker) StartTask() { 28 | fmt.Println("I will start a task") 29 | } 30 | 31 | func (w *Worker) StopTask() { 32 | fmt.Println("I Will stop a task") 33 | } 34 | -------------------------------------------------------------------------------- /ch3/worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/golang-collections/collections/queue" 7 | "github.com/google/uuid" 8 | 9 | "cube/task" 10 | ) 11 | 12 | type Worker struct { 13 | Name string 14 | Queue queue.Queue 15 | Db map[uuid.UUID]task.Task 16 | TaskCount int 17 | } 18 | 19 | func (w *Worker) CollectStats() { 20 | fmt.Println("I will collect stats") 21 | } 22 | 23 | func (w *Worker) RunTask() { 24 | fmt.Println("I will start or stop a task") 25 | } 26 | 27 | func (w *Worker) StartTask() { 28 | fmt.Println("I will start a task") 29 | } 30 | 31 | func (w *Worker) StopTask() { 32 | fmt.Println("I Will stop a task") 33 | } 34 | -------------------------------------------------------------------------------- /ch4/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | type State int 4 | 5 | const ( 6 | Pending State = iota 7 | Scheduled 8 | Running 9 | Completed 10 | Failed 11 | ) 12 | 13 | var stateTransitionMap = map[State][]State{ 14 | Pending: []State{Scheduled}, 15 | Scheduled: []State{Scheduled, Running, Failed}, 16 | Running: []State{Running, Completed, Failed}, 17 | Completed: []State{}, 18 | Failed: []State{}, 19 | } 20 | 21 | func Contains(states []State, state State) bool { 22 | for _, s := range states { 23 | if s == state { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | 30 | func ValidStateTransition(src State, dst State) bool { 31 | return Contains(stateTransitionMap[src], dst) 32 | } 33 | -------------------------------------------------------------------------------- /ch5/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | type State int 4 | 5 | const ( 6 | Pending State = iota 7 | Scheduled 8 | Running 9 | Completed 10 | Failed 11 | ) 12 | 13 | var stateTransitionMap = map[State][]State{ 14 | Pending: []State{Scheduled}, 15 | Scheduled: []State{Scheduled, Running, Failed}, 16 | Running: []State{Running, Completed, Failed}, 17 | Completed: []State{}, 18 | Failed: []State{}, 19 | } 20 | 21 | func Contains(states []State, state State) bool { 22 | for _, s := range states { 23 | if s == state { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | 30 | func ValidStateTransition(src State, dst State) bool { 31 | return Contains(stateTransitionMap[src], dst) 32 | } 33 | -------------------------------------------------------------------------------- /ch6/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | type State int 4 | 5 | const ( 6 | Pending State = iota 7 | Scheduled 8 | Running 9 | Completed 10 | Failed 11 | ) 12 | 13 | var stateTransitionMap = map[State][]State{ 14 | Pending: []State{Scheduled}, 15 | Scheduled: []State{Scheduled, Running, Failed}, 16 | Running: []State{Running, Completed, Failed}, 17 | Completed: []State{}, 18 | Failed: []State{}, 19 | } 20 | 21 | func Contains(states []State, state State) bool { 22 | for _, s := range states { 23 | if s == state { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | 30 | func ValidStateTransition(src State, dst State) bool { 31 | return Contains(stateTransitionMap[src], dst) 32 | } 33 | -------------------------------------------------------------------------------- /ch2/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "fmt" 6 | 7 | "github.com/golang-collections/collections/queue" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type Manager struct { 12 | Pending queue.Queue 13 | TaskDb map[string][]*task.Task 14 | EventDb map[string][]*task.TaskEvent 15 | Workers []string 16 | WorkerTaskMap map[string][]uuid.UUID 17 | TaskWorkerMap map[uuid.UUID]string 18 | } 19 | 20 | func (m *Manager) SelectWorker() { 21 | fmt.Println("I will select an appropriate worker") 22 | } 23 | 24 | func (m *Manager) UpdateTasks() { 25 | fmt.Println("I will update tasks") 26 | } 27 | 28 | func (m *Manager) SendWork() { 29 | fmt.Println("I will send work to workers") 30 | } 31 | -------------------------------------------------------------------------------- /ch2/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.1 // indirect 7 | github.com/containerd/containerd v1.5.9 // indirect 8 | github.com/docker/docker v20.10.12+incompatible // indirect 9 | github.com/docker/go-connections v0.4.0 // indirect 10 | github.com/docker/go-units v0.4.0 // indirect 11 | github.com/gogo/protobuf v1.3.2 // indirect 12 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect 13 | github.com/google/uuid v1.2.0 // indirect 14 | github.com/moby/moby v20.10.12+incompatible // indirect 15 | github.com/opencontainers/go-digest v1.0.0 // indirect 16 | github.com/opencontainers/image-spec v1.0.2 // indirect 17 | google.golang.org/grpc v1.43.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /ch6/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 // indirect 8 | github.com/containerd/containerd v1.5.1 // indirect 9 | github.com/docker/docker v20.10.6+incompatible 10 | github.com/docker/go-connections v0.4.0 11 | github.com/go-chi/chi/v5 v5.0.4 12 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 13 | github.com/google/uuid v1.2.0 14 | github.com/moby/moby v20.10.6+incompatible 15 | github.com/morikuni/aec v1.0.0 // indirect 16 | github.com/sirupsen/logrus v1.8.1 // indirect 17 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 18 | google.golang.org/grpc v1.37.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /ch7/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 // indirect 8 | github.com/containerd/containerd v1.5.1 // indirect 9 | github.com/docker/docker v20.10.6+incompatible 10 | github.com/docker/go-connections v0.4.0 11 | github.com/go-chi/chi/v5 v5.0.4 12 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 13 | github.com/google/uuid v1.2.0 14 | github.com/moby/moby v20.10.6+incompatible 15 | github.com/morikuni/aec v1.0.0 // indirect 16 | github.com/sirupsen/logrus v1.8.1 // indirect 17 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 18 | google.golang.org/grpc v1.37.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /ch8/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 // indirect 8 | github.com/containerd/containerd v1.5.1 // indirect 9 | github.com/docker/docker v20.10.6+incompatible 10 | github.com/docker/go-connections v0.4.0 11 | github.com/go-chi/chi/v5 v5.0.4 12 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 13 | github.com/google/uuid v1.2.0 14 | github.com/moby/moby v20.10.6+incompatible 15 | github.com/morikuni/aec v1.0.0 // indirect 16 | github.com/sirupsen/logrus v1.8.1 // indirect 17 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 18 | google.golang.org/grpc v1.37.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /ch9/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 // indirect 8 | github.com/containerd/containerd v1.5.1 // indirect 9 | github.com/docker/docker v20.10.6+incompatible 10 | github.com/docker/go-connections v0.4.0 11 | github.com/go-chi/chi/v5 v5.0.4 12 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 13 | github.com/google/uuid v1.2.0 14 | github.com/moby/moby v20.10.6+incompatible 15 | github.com/morikuni/aec v1.0.0 // indirect 16 | github.com/sirupsen/logrus v1.8.1 // indirect 17 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 18 | google.golang.org/grpc v1.37.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /ch5/worker/api.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Worker *Worker 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | } 32 | 33 | func (a *Api) Start() { 34 | a.initRouter() 35 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 36 | } 37 | -------------------------------------------------------------------------------- /ch7/worker/api.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Worker *Worker 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | } 32 | 33 | func (a *Api) Start() { 34 | a.initRouter() 35 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 36 | } 37 | -------------------------------------------------------------------------------- /ch8/worker/api.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Worker *Worker 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | } 32 | 33 | func (a *Api) Start() { 34 | a.initRouter() 35 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 36 | } 37 | -------------------------------------------------------------------------------- /ch10/manager/api.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Manager *Manager 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | } 32 | 33 | func (a *Api) Start() { 34 | a.initRouter() 35 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 36 | } 37 | -------------------------------------------------------------------------------- /ch11/manager/api.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Manager *Manager 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | } 32 | 33 | func (a *Api) Start() { 34 | a.initRouter() 35 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 36 | } 37 | -------------------------------------------------------------------------------- /ch3/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "fmt" 6 | 7 | "github.com/golang-collections/collections/queue" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type Manager struct { 12 | Pending queue.Queue 13 | TaskDb map[string][]task.Task 14 | EventDb map[string][]task.TaskEvent 15 | Workers []string 16 | WorkerTaskMap map[string][]uuid.UUID 17 | TaskWorkerMap map[uuid.UUID]string 18 | //stateMachine task.StateMachine 19 | } 20 | 21 | func (m *Manager) SelectWorker() { 22 | fmt.Println("I will select an appropriate worker") 23 | } 24 | 25 | func (m *Manager) UpdateTasks() { 26 | fmt.Println("I will update tasks") 27 | } 28 | 29 | func (m *Manager) SendWork() { 30 | fmt.Println("I will send work to workers") 31 | } 32 | -------------------------------------------------------------------------------- /ch4/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "fmt" 6 | 7 | "github.com/golang-collections/collections/queue" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type Manager struct { 12 | Pending queue.Queue 13 | TaskDb map[string][]task.Task 14 | EventDb map[string][]task.TaskEvent 15 | Workers []string 16 | WorkerTaskMap map[string][]uuid.UUID 17 | TaskWorkerMap map[uuid.UUID]string 18 | //stateMachine task.StateMachine 19 | } 20 | 21 | func (m *Manager) SelectWorker() { 22 | fmt.Println("I will select an appropriate worker") 23 | } 24 | 25 | func (m *Manager) UpdateTasks() { 26 | fmt.Println("I will update tasks") 27 | } 28 | 29 | func (m *Manager) SendWork() { 30 | fmt.Println("I will send work to workers") 31 | } 32 | -------------------------------------------------------------------------------- /ch5/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "fmt" 6 | 7 | "github.com/golang-collections/collections/queue" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type Manager struct { 12 | Pending queue.Queue 13 | TaskDb map[string][]task.Task 14 | EventDb map[string][]task.TaskEvent 15 | Workers []string 16 | WorkerTaskMap map[string][]uuid.UUID 17 | TaskWorkerMap map[uuid.UUID]string 18 | //stateMachine task.StateMachine 19 | } 20 | 21 | func (m *Manager) SelectWorker() { 22 | fmt.Println("I will select an appropriate worker") 23 | } 24 | 25 | func (m *Manager) UpdateTasks() { 26 | fmt.Println("I will update tasks") 27 | } 28 | 29 | func (m *Manager) SendWork() { 30 | fmt.Println("I will send work to workers") 31 | } 32 | -------------------------------------------------------------------------------- /ch6/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "fmt" 6 | 7 | "github.com/golang-collections/collections/queue" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type Manager struct { 12 | Pending queue.Queue 13 | TaskDb map[string][]task.Task 14 | EventDb map[string][]task.TaskEvent 15 | Workers []string 16 | WorkerTaskMap map[string][]uuid.UUID 17 | TaskWorkerMap map[uuid.UUID]string 18 | //stateMachine task.StateMachine 19 | } 20 | 21 | func (m *Manager) SelectWorker() { 22 | fmt.Println("I will select an appropriate worker") 23 | } 24 | 25 | func (m *Manager) UpdateTasks() { 26 | fmt.Println("I will update tasks") 27 | } 28 | 29 | func (m *Manager) SendWork() { 30 | fmt.Println("I will send work to workers") 31 | } 32 | -------------------------------------------------------------------------------- /ch8/manager/api.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Manager *Manager 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | } 32 | 33 | func (a *Api) Start() { 34 | a.initRouter() 35 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 36 | } 37 | -------------------------------------------------------------------------------- /ch9/manager/api.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Manager *Manager 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | } 32 | 33 | func (a *Api) Start() { 34 | a.initRouter() 35 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 36 | } 37 | -------------------------------------------------------------------------------- /ch2/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/docker/go-connections/nat" 7 | "github.com/google/uuid" 8 | ) 9 | 10 | type State int 11 | 12 | const ( 13 | Pending State = iota 14 | Scheduled 15 | Running 16 | Completed 17 | Failed 18 | ) 19 | 20 | type Task struct { 21 | ID uuid.UUID 22 | ContainerID string 23 | Name string 24 | State State 25 | Image string 26 | Cpu float64 27 | Memory int64 28 | Disk int64 29 | ExposedPorts nat.PortSet 30 | PortBindings map[string]string 31 | RestartPolicy string 32 | StartTime time.Time 33 | FinishTime time.Time 34 | } 35 | 36 | type TaskEvent struct { 37 | ID uuid.UUID 38 | State State 39 | Timestamp time.Time 40 | Task Task 41 | } 42 | -------------------------------------------------------------------------------- /ch7/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "log" 4 | 5 | type State int 6 | 7 | const ( 8 | Pending State = iota 9 | Scheduled 10 | Running 11 | Completed 12 | Failed 13 | ) 14 | 15 | var stateTransitionMap = map[State][]State{ 16 | Pending: []State{Scheduled}, 17 | Scheduled: []State{Scheduled, Running, Failed}, 18 | Running: []State{Running, Completed, Failed}, 19 | Completed: []State{}, 20 | Failed: []State{}, 21 | } 22 | 23 | func Contains(states []State, state State) bool { 24 | for _, s := range states { 25 | if s == state { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | func ValidStateTransition(src State, dst State) bool { 33 | log.Printf("attempting to transition from %#v to %#v\n", src, dst) 34 | return Contains(stateTransitionMap[src], dst) 35 | } 36 | -------------------------------------------------------------------------------- /ch8/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "log" 4 | 5 | type State int 6 | 7 | const ( 8 | Pending State = iota 9 | Scheduled 10 | Running 11 | Completed 12 | Failed 13 | ) 14 | 15 | var stateTransitionMap = map[State][]State{ 16 | Pending: []State{Scheduled}, 17 | Scheduled: []State{Scheduled, Running, Failed}, 18 | Running: []State{Running, Completed, Failed}, 19 | Completed: []State{}, 20 | Failed: []State{}, 21 | } 22 | 23 | func Contains(states []State, state State) bool { 24 | for _, s := range states { 25 | if s == state { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | func ValidStateTransition(src State, dst State) bool { 33 | log.Printf("attempting to transition from %#v to %#v\n", src, dst) 34 | return Contains(stateTransitionMap[src], dst) 35 | } 36 | -------------------------------------------------------------------------------- /ch9/worker/api.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Worker *Worker 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | r.Get("/", a.InspectTaskHandler) 30 | }) 31 | }) 32 | } 33 | 34 | func (a *Api) Start() { 35 | a.initRouter() 36 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 37 | } 38 | -------------------------------------------------------------------------------- /ch10/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "log" 4 | 5 | type State int 6 | 7 | const ( 8 | Pending State = iota 9 | Scheduled 10 | Running 11 | Completed 12 | Failed 13 | ) 14 | 15 | var stateTransitionMap = map[State][]State{ 16 | Pending: []State{Scheduled}, 17 | Scheduled: []State{Scheduled, Running, Failed}, 18 | Running: []State{Running, Completed, Failed, Scheduled}, 19 | Completed: []State{}, 20 | Failed: []State{Scheduled}, 21 | } 22 | 23 | func Contains(states []State, state State) bool { 24 | for _, s := range states { 25 | if s == state { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | func ValidStateTransition(src State, dst State) bool { 33 | log.Printf("attempting to transition from %#v to %#v\n", src, dst) 34 | return Contains(stateTransitionMap[src], dst) 35 | } 36 | -------------------------------------------------------------------------------- /ch11/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "log" 4 | 5 | type State int 6 | 7 | const ( 8 | Pending State = iota 9 | Scheduled 10 | Running 11 | Completed 12 | Failed 13 | ) 14 | 15 | var stateTransitionMap = map[State][]State{ 16 | Pending: []State{Scheduled}, 17 | Scheduled: []State{Scheduled, Running, Failed}, 18 | Running: []State{Running, Completed, Failed, Scheduled}, 19 | Completed: []State{}, 20 | Failed: []State{Scheduled}, 21 | } 22 | 23 | func Contains(states []State, state State) bool { 24 | for _, s := range states { 25 | if s == state { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | func ValidStateTransition(src State, dst State) bool { 33 | log.Printf("attempting to transition from %#v to %#v\n", src, dst) 34 | return Contains(stateTransitionMap[src], dst) 35 | } 36 | -------------------------------------------------------------------------------- /ch9/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "log" 4 | 5 | type State int 6 | 7 | const ( 8 | Pending State = iota 9 | Scheduled 10 | Running 11 | Completed 12 | Failed 13 | ) 14 | 15 | var stateTransitionMap = map[State][]State{ 16 | Pending: []State{Scheduled}, 17 | Scheduled: []State{Scheduled, Running, Failed}, 18 | Running: []State{Running, Completed, Failed, Scheduled}, 19 | Completed: []State{}, 20 | Failed: []State{Scheduled}, 21 | } 22 | 23 | func Contains(states []State, state State) bool { 24 | for _, s := range states { 25 | if s == state { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | func ValidStateTransition(src State, dst State) bool { 33 | log.Printf("attempting to transition from %#v to %#v\n", src, dst) 34 | return Contains(stateTransitionMap[src], dst) 35 | } 36 | -------------------------------------------------------------------------------- /ch10/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.5.0 // indirect 7 | github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 8 | github.com/containerd/containerd v1.5.1 // indirect 9 | github.com/docker/distribution v2.7.1+incompatible 10 | github.com/docker/docker v20.10.6+incompatible 11 | github.com/docker/go-connections v0.4.0 12 | github.com/go-chi/chi/v5 v5.0.4 13 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 14 | github.com/google/go-cmp v0.5.4 15 | github.com/google/uuid v1.2.0 16 | github.com/moby/moby v20.10.6+incompatible 17 | github.com/morikuni/aec v1.0.0 // indirect 18 | github.com/sirupsen/logrus v1.8.1 // indirect 19 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 20 | google.golang.org/grpc v1.37.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /ch6/worker/api.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Worker *Worker 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | a.Router.Route("/stats", func(r chi.Router) { 32 | r.Get("/", a.GetStatsHandler) 33 | }) 34 | } 35 | 36 | func (a *Api) Start() { 37 | a.initRouter() 38 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 39 | } 40 | -------------------------------------------------------------------------------- /ch12/manager/api.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Manager *Manager 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | }) 30 | }) 31 | a.Router.Route("/nodes", func(r chi.Router) { 32 | r.Get("/", a.GetNodesHandler) 33 | }) 34 | } 35 | 36 | func (a *Api) Start() { 37 | a.initRouter() 38 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 39 | } 40 | -------------------------------------------------------------------------------- /ch10/worker/api.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Worker *Worker 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | r.Get("/", a.InspectTaskHandler) 30 | }) 31 | }) 32 | a.Router.Route("/stats", func(r chi.Router) { 33 | r.Get("/", a.GetStatsHandler) 34 | }) 35 | } 36 | 37 | func (a *Api) Start() { 38 | a.initRouter() 39 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 40 | } 41 | -------------------------------------------------------------------------------- /ch11/worker/api.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Worker *Worker 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | r.Get("/", a.InspectTaskHandler) 30 | }) 31 | }) 32 | a.Router.Route("/stats", func(r chi.Router) { 33 | r.Get("/", a.GetStatsHandler) 34 | }) 35 | } 36 | 37 | func (a *Api) Start() { 38 | a.initRouter() 39 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 40 | } 41 | -------------------------------------------------------------------------------- /ch12/worker/api.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | type ErrResponse struct { 11 | HTTPStatusCode int 12 | Message string 13 | } 14 | 15 | type Api struct { 16 | Address string 17 | Port int 18 | Worker *Worker 19 | Router *chi.Mux 20 | } 21 | 22 | func (a *Api) initRouter() { 23 | a.Router = chi.NewRouter() 24 | a.Router.Route("/tasks", func(r chi.Router) { 25 | r.Post("/", a.StartTaskHandler) 26 | r.Get("/", a.GetTasksHandler) 27 | r.Route("/{taskID}", func(r chi.Router) { 28 | r.Delete("/", a.StopTaskHandler) 29 | r.Get("/", a.InspectTaskHandler) 30 | }) 31 | }) 32 | a.Router.Route("/stats", func(r chi.Router) { 33 | r.Get("/", a.GetStatsHandler) 34 | }) 35 | } 36 | 37 | func (a *Api) Start() { 38 | a.initRouter() 39 | http.ListenAndServe(fmt.Sprintf("%s:%d", a.Address, a.Port), a.Router) 40 | } 41 | -------------------------------------------------------------------------------- /ch12/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "log" 4 | 5 | type State int 6 | 7 | const ( 8 | Pending State = iota 9 | Scheduled 10 | Running 11 | Completed 12 | Failed 13 | ) 14 | 15 | func (s State) String() []string { 16 | return []string{"Pending", "Scheduled", "Running", "Completed", "Failed"} 17 | } 18 | 19 | var stateTransitionMap = map[State][]State{ 20 | Pending: []State{Scheduled}, 21 | Scheduled: []State{Scheduled, Running, Failed}, 22 | Running: []State{Running, Completed, Failed, Scheduled}, 23 | Completed: []State{}, 24 | Failed: []State{Scheduled}, 25 | } 26 | 27 | func Contains(states []State, state State) bool { 28 | for _, s := range states { 29 | if s == state { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | func ValidStateTransition(src State, dst State) bool { 37 | log.Printf("attempting to transition from %#v to %#v\n", src, dst) 38 | return Contains(stateTransitionMap[src], dst) 39 | } 40 | -------------------------------------------------------------------------------- /ch5/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/task" 5 | "cube/worker" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/golang-collections/collections/queue" 13 | "github.com/google/uuid" 14 | ) 15 | 16 | func main() { 17 | host := os.Getenv("CUBE_HOST") 18 | port, _ := strconv.Atoi(os.Getenv("CUBE_PORT")) 19 | 20 | fmt.Println("Starting Cube worker") 21 | 22 | w := worker.Worker{ 23 | Queue: *queue.New(), 24 | Db: make(map[uuid.UUID]*task.Task), 25 | } 26 | api := worker.Api{Address: host, Port: port, Worker: &w} 27 | 28 | go runTasks(&w) 29 | api.Start() 30 | } 31 | 32 | func runTasks(w *worker.Worker) { 33 | for { 34 | if w.Queue.Len() != 0 { 35 | result := w.RunTask() 36 | if result.Error != nil { 37 | log.Printf("Error running task: %v\n", result.Error) 38 | } 39 | } else { 40 | log.Printf("No tasks to process currently.\n") 41 | } 42 | log.Println("Sleeping for 10 seconds.") 43 | time.Sleep(10 * time.Second) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /ch6/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/task" 5 | "cube/worker" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/golang-collections/collections/queue" 13 | "github.com/google/uuid" 14 | ) 15 | 16 | func main() { 17 | host := os.Getenv("CUBE_HOST") 18 | port, _ := strconv.Atoi(os.Getenv("CUBE_PORT")) 19 | 20 | fmt.Println("Starting Cube worker") 21 | 22 | w := worker.Worker{ 23 | Queue: *queue.New(), 24 | Db: make(map[uuid.UUID]*task.Task), 25 | } 26 | api := worker.Api{Address: host, Port: port, Worker: &w} 27 | 28 | go runTasks(&w) 29 | go w.CollectStats() 30 | api.Start() 31 | } 32 | 33 | func runTasks(w *worker.Worker) { 34 | for { 35 | if w.Queue.Len() != 0 { 36 | result := w.RunTask() 37 | if result.Error != nil { 38 | log.Printf("Error running task: %v\n", result.Error) 39 | } 40 | } else { 41 | log.Printf("No tasks to process currently.\n") 42 | } 43 | log.Println("Sleeping for 10 seconds.") 44 | time.Sleep(10 * time.Second) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /ch8/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/manager" 5 | "cube/task" 6 | "cube/worker" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/golang-collections/collections/queue" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func main() { 16 | whost := os.Getenv("CUBE_WORKER_HOST") 17 | wport, _ := strconv.Atoi(os.Getenv("CUBE_WORKER_PORT")) 18 | 19 | mhost := os.Getenv("CUBE_MANAGER_HOST") 20 | mport, _ := strconv.Atoi(os.Getenv("CUBE_MANAGER_PORT")) 21 | 22 | fmt.Println("Starting Cube worker") 23 | 24 | w := worker.Worker{ 25 | Queue: *queue.New(), 26 | Db: make(map[uuid.UUID]*task.Task), 27 | } 28 | wapi := worker.Api{Address: whost, Port: wport, Worker: &w} 29 | 30 | go w.RunTasks() 31 | go w.CollectStats() 32 | go wapi.Start() 33 | 34 | fmt.Println("Starting Cube manager") 35 | 36 | workers := []string{fmt.Sprintf("%s:%d", whost, wport)} 37 | m := manager.New(workers) 38 | mapi := manager.Api{Address: mhost, Port: mport, Manager: m} 39 | 40 | go m.ProcessTasks() 41 | go m.UpdateTasks() 42 | 43 | mapi.Start() 44 | 45 | } 46 | -------------------------------------------------------------------------------- /ch4/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/task" 5 | "cube/worker" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/golang-collections/collections/queue" 10 | "github.com/google/uuid" 11 | ) 12 | 13 | func main() { 14 | db := make(map[uuid.UUID]*task.Task) 15 | w := worker.Worker{ 16 | Queue: *queue.New(), 17 | Db: db, 18 | } 19 | 20 | t := task.Task{ 21 | ID: uuid.New(), 22 | Name: "test-container-1", 23 | State: task.Scheduled, 24 | Image: "strm/helloworld-http", 25 | } 26 | 27 | // first time the worker will see the task 28 | fmt.Println("starting task") 29 | w.AddTask(t) 30 | result := w.RunTask() 31 | if result.Error != nil { 32 | panic(result.Error) 33 | } 34 | 35 | t.ContainerID = result.ContainerId 36 | 37 | fmt.Printf("task %s is running in container %s\n", t.ID, t.ContainerID) 38 | fmt.Println("Sleepy time") 39 | time.Sleep(time.Second * 30) 40 | 41 | fmt.Printf("stopping task %s\n", t.ID) 42 | t.State = task.Completed 43 | w.AddTask(t) 44 | result = w.RunTask() 45 | if result.Error != nil { 46 | panic(result.Error) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ch9/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/manager" 5 | "cube/task" 6 | "cube/worker" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/golang-collections/collections/queue" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func main() { 16 | whost := os.Getenv("CUBE_WORKER_HOST") 17 | wport, _ := strconv.Atoi(os.Getenv("CUBE_WORKER_PORT")) 18 | 19 | mhost := os.Getenv("CUBE_MANAGER_HOST") 20 | mport, _ := strconv.Atoi(os.Getenv("CUBE_MANAGER_PORT")) 21 | 22 | fmt.Println("Starting Cube worker") 23 | 24 | w := worker.Worker{ 25 | Queue: *queue.New(), 26 | Db: make(map[uuid.UUID]*task.Task), 27 | } 28 | wapi := worker.Api{Address: whost, Port: wport, Worker: &w} 29 | 30 | go w.RunTasks() 31 | go w.CollectStats() 32 | go w.UpdateTasks() 33 | go wapi.Start() 34 | 35 | fmt.Println("Starting Cube manager") 36 | 37 | workers := []string{fmt.Sprintf("%s:%d", whost, wport)} 38 | m := manager.New(workers) 39 | mapi := manager.Api{Address: mhost, Port: mport, Manager: m} 40 | 41 | go m.ProcessTasks() 42 | go m.UpdateTasks() 43 | go m.DoHealthChecks() 44 | 45 | mapi.Start() 46 | 47 | } 48 | -------------------------------------------------------------------------------- /ch12/cmd/stop.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | rootCmd.AddCommand(stopCmd) 13 | stopCmd.Flags().StringP("manager", "m", "localhost:5555", "Manager to talk to") 14 | } 15 | 16 | var stopCmd = &cobra.Command{ 17 | Use: "stop", 18 | Short: "Stop a running task.", 19 | Long: `cube stop command. 20 | 21 | The stop command stops a running task.`, 22 | Args: cobra.MinimumNArgs(1), 23 | Run: func(cmd *cobra.Command, args []string) { 24 | manager, _ := cmd.Flags().GetString("manager") 25 | url := fmt.Sprintf("http://%s/tasks/%s", manager, args[0]) 26 | client := &http.Client{} 27 | req, err := http.NewRequest("DELETE", url, nil) 28 | if err != nil { 29 | log.Printf("Error creating request %v: %v", url, err) 30 | } 31 | 32 | resp, err := client.Do(req) 33 | if err != nil { 34 | log.Printf("Error connecting to %v: %v", url, err) 35 | } 36 | 37 | if resp.StatusCode != http.StatusNoContent { 38 | log.Printf("Error sending request: %v", err) 39 | } 40 | 41 | log.Printf("Task %v has been stopped.", args[0]) 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /ch12/cmd/node.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cube/node" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "text/tabwriter" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func init() { 16 | rootCmd.AddCommand(nodeCmd) 17 | nodeCmd.Flags().StringP("manager", "m", "localhost:5555", "Manager to talk to") 18 | } 19 | 20 | var nodeCmd = &cobra.Command{ 21 | Use: "node", 22 | Short: "Node command to list nodes.", 23 | Long: `cube node command. 24 | 25 | The node command allows a user to get the information about the nodes in the cluster.`, 26 | Run: func(cmd *cobra.Command, args []string) { 27 | manager, _ := cmd.Flags().GetString("manager") 28 | 29 | url := fmt.Sprintf("http://%s/nodes", manager) 30 | resp, _ := http.Get(url) 31 | defer resp.Body.Close() 32 | body, _ := io.ReadAll(resp.Body) 33 | var nodes []*node.Node 34 | json.Unmarshal(body, &nodes) 35 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', tabwriter.TabIndent) 36 | fmt.Fprintln(w, "NAME\tMEMORY (MiB)\tDISK (GiB)\tROLE\tTASKS\t") 37 | for _, node := range nodes { 38 | fmt.Fprintf(w, "%s\t%d\t%d\t%s\t%d\t\n", node.Name, node.Memory/1000, node.Disk/1000/1000/1000, node.Role, node.TaskCount) 39 | } 40 | w.Flush() 41 | }, 42 | } 43 | -------------------------------------------------------------------------------- /ch12/cmd/worker.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cube/worker" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/google/uuid" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func init() { 13 | rootCmd.AddCommand(workerCmd) 14 | workerCmd.Flags().StringP("host", "H", "0.0.0.0", "Hostname or IP address") 15 | workerCmd.Flags().IntP("port", "p", 5556, "Port on which to listen") 16 | workerCmd.Flags().StringP("name", "n", fmt.Sprintf("worker-%s", uuid.New().String()), "Name of the worker") 17 | workerCmd.Flags().StringP("dbtype", "d", "memory", "Type of datastore to use for tasks (\"memory\" or \"persistent\")") 18 | } 19 | 20 | var workerCmd = &cobra.Command{ 21 | Use: "worker", 22 | Short: "Worker command to operate a Cube worker node.", 23 | Long: `cube worker command. 24 | 25 | The worker runs tasks and responds to the manager's requests about task state.`, 26 | Run: func(cmd *cobra.Command, args []string) { 27 | host, _ := cmd.Flags().GetString("host") 28 | port, _ := cmd.Flags().GetInt("port") 29 | name, _ := cmd.Flags().GetString("name") 30 | dbType, _ := cmd.Flags().GetString("dbtype") 31 | 32 | log.Println("Starting worker.") 33 | w := worker.New(name, dbType) 34 | api := worker.Api{Address: host, Port: port, Worker: w} 35 | go w.RunTasks() 36 | go w.CollectStats() 37 | go w.UpdateTasks() 38 | log.Printf("Starting worker API on http://%s:%d", host, port) 39 | api.Start() 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /ch11/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/boltdb/bolt v1.3.1 7 | github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 8 | github.com/docker/docker v20.10.6+incompatible 9 | github.com/docker/go-connections v0.4.0 10 | github.com/go-chi/chi/v5 v5.0.4 11 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 12 | github.com/google/go-cmp v0.5.4 13 | github.com/google/uuid v1.2.0 14 | github.com/moby/moby v20.10.6+incompatible 15 | ) 16 | 17 | require ( 18 | github.com/Microsoft/go-winio v0.5.0 // indirect 19 | github.com/containerd/containerd v1.5.1 // indirect 20 | github.com/docker/distribution v2.7.1+incompatible // indirect 21 | github.com/docker/go-units v0.4.0 // indirect 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/golang/protobuf v1.4.3 // indirect 24 | github.com/morikuni/aec v1.0.0 // indirect 25 | github.com/opencontainers/go-digest v1.0.0 // indirect 26 | github.com/opencontainers/image-spec v1.0.1 // indirect 27 | github.com/pkg/errors v0.9.1 // indirect 28 | github.com/sirupsen/logrus v1.8.1 // indirect 29 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 30 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect 31 | google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect 32 | google.golang.org/grpc v1.37.1 // indirect 33 | google.golang.org/protobuf v1.25.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /ch2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/node" 5 | "cube/task" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/golang-collections/collections/queue" 10 | "github.com/google/uuid" 11 | 12 | "cube/manager" 13 | "cube/worker" 14 | ) 15 | 16 | func main() { 17 | t := task.Task{ 18 | ID: uuid.New(), 19 | Name: "Task-1", 20 | State: task.Pending, 21 | Image: "Image-1", 22 | Memory: 1024, 23 | Disk: 1, 24 | } 25 | 26 | te := task.TaskEvent{ 27 | ID: uuid.New(), 28 | State: task.Pending, 29 | Timestamp: time.Now(), 30 | Task: t, 31 | } 32 | 33 | fmt.Printf("task: %v\n", t) 34 | fmt.Printf("task event: %v\n", te) 35 | 36 | w := worker.Worker{ 37 | Name: "worker-1", 38 | Queue: *queue.New(), 39 | Db: make(map[uuid.UUID]*task.Task), 40 | } 41 | fmt.Printf("worker: %v\n", w) 42 | w.CollectStats() 43 | w.RunTask() 44 | w.StartTask() 45 | w.StopTask() 46 | 47 | m := manager.Manager{ 48 | Pending: *queue.New(), 49 | TaskDb: make(map[string][]*task.Task), 50 | EventDb: make(map[string][]*task.TaskEvent), 51 | Workers: []string{w.Name}, 52 | } 53 | 54 | fmt.Printf("manager: %v\n", m) 55 | m.SelectWorker() 56 | m.UpdateTasks() 57 | m.SendWork() 58 | 59 | n := node.Node{ 60 | Name: "Node-1", 61 | Ip: "192.168.1.1", 62 | Cores: 4, 63 | Memory: 1024, 64 | Disk: 25, 65 | Role: "worker", 66 | } 67 | 68 | fmt.Printf("node: %v\n", n) 69 | } 70 | -------------------------------------------------------------------------------- /ch10/echo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | 12 | "github.com/go-chi/chi/v5" 13 | ) 14 | 15 | type Message struct { 16 | Msg string 17 | } 18 | 19 | func main() { 20 | r := chi.NewRouter() 21 | r.Post("/", func(w http.ResponseWriter, r *http.Request) { 22 | d := json.NewDecoder(r.Body) 23 | m := Message{} 24 | err := d.Decode(&m) 25 | if err != nil { 26 | json.NewEncoder(w).Encode(errors.New("Unable to decode request body")) 27 | return 28 | } 29 | log.Printf("Received message: %v\n", m.Msg) 30 | 31 | json.NewEncoder(w).Encode(m) 32 | }) 33 | r.Get("/health", func(w http.ResponseWriter, r *http.Request) { 34 | log.Printf("Health check called") 35 | w.Write([]byte("OK")) 36 | }) 37 | r.Get("/healthfail", func(w http.ResponseWriter, r *http.Request) { 38 | log.Printf("Health check failed") 39 | w.WriteHeader(http.StatusInternalServerError) 40 | w.Write([]byte("Internal server error")) 41 | }) 42 | 43 | srv := &http.Server{ 44 | Addr: "0.0.0.0:7777", 45 | Handler: r, 46 | } 47 | 48 | go func() { 49 | log.Println("Listening on http://localhost:7777") 50 | if err := srv.ListenAndServe(); err != nil { 51 | log.Println(err) 52 | } 53 | }() 54 | 55 | // Setup handler for graceful shutdown 56 | c := make(chan os.Signal, 1) 57 | signal.Notify(c, os.Interrupt, syscall.SIGKILL, syscall.SIGTERM) 58 | <-c 59 | 60 | log.Println("Shutting down") 61 | } 62 | -------------------------------------------------------------------------------- /ch11/echo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | 12 | "github.com/go-chi/chi/v5" 13 | ) 14 | 15 | type Message struct { 16 | Msg string 17 | } 18 | 19 | func main() { 20 | r := chi.NewRouter() 21 | r.Post("/", func(w http.ResponseWriter, r *http.Request) { 22 | d := json.NewDecoder(r.Body) 23 | m := Message{} 24 | err := d.Decode(&m) 25 | if err != nil { 26 | json.NewEncoder(w).Encode(errors.New("Unable to decode request body")) 27 | return 28 | } 29 | log.Printf("Received message: %v\n", m.Msg) 30 | 31 | json.NewEncoder(w).Encode(m) 32 | }) 33 | r.Get("/health", func(w http.ResponseWriter, r *http.Request) { 34 | log.Printf("Health check called") 35 | w.Write([]byte("OK")) 36 | }) 37 | r.Get("/healthfail", func(w http.ResponseWriter, r *http.Request) { 38 | log.Printf("Health check failed") 39 | w.WriteHeader(http.StatusInternalServerError) 40 | w.Write([]byte("Internal server error")) 41 | }) 42 | 43 | srv := &http.Server{ 44 | Addr: "0.0.0.0:7777", 45 | Handler: r, 46 | } 47 | 48 | go func() { 49 | log.Println("Listening on http://localhost:7777") 50 | if err := srv.ListenAndServe(); err != nil { 51 | log.Println(err) 52 | } 53 | }() 54 | 55 | // Setup handler for graceful shutdown 56 | c := make(chan os.Signal, 1) 57 | signal.Notify(c, os.Interrupt, syscall.SIGKILL, syscall.SIGTERM) 58 | <-c 59 | 60 | log.Println("Shutting down") 61 | } 62 | -------------------------------------------------------------------------------- /ch12/echo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | 12 | "github.com/go-chi/chi/v5" 13 | ) 14 | 15 | type Message struct { 16 | Msg string 17 | } 18 | 19 | func main() { 20 | r := chi.NewRouter() 21 | r.Post("/", func(w http.ResponseWriter, r *http.Request) { 22 | d := json.NewDecoder(r.Body) 23 | m := Message{} 24 | err := d.Decode(&m) 25 | if err != nil { 26 | json.NewEncoder(w).Encode(errors.New("Unable to decode request body")) 27 | return 28 | } 29 | log.Printf("Received message: %v\n", m.Msg) 30 | 31 | json.NewEncoder(w).Encode(m) 32 | }) 33 | r.Get("/health", func(w http.ResponseWriter, r *http.Request) { 34 | log.Printf("Health check called") 35 | w.Write([]byte("OK")) 36 | }) 37 | r.Get("/healthfail", func(w http.ResponseWriter, r *http.Request) { 38 | log.Printf("Health check failed") 39 | w.WriteHeader(http.StatusInternalServerError) 40 | w.Write([]byte("Internal server error")) 41 | }) 42 | 43 | srv := &http.Server{ 44 | Addr: "0.0.0.0:7777", 45 | Handler: r, 46 | } 47 | 48 | go func() { 49 | log.Println("Listening on http://localhost:7777") 50 | if err := srv.ListenAndServe(); err != nil { 51 | log.Println(err) 52 | } 53 | }() 54 | 55 | // Setup handler for graceful shutdown 56 | c := make(chan os.Signal, 1) 57 | signal.Notify(c, os.Interrupt, syscall.SIGKILL, syscall.SIGTERM) 58 | <-c 59 | 60 | log.Println("Shutting down") 61 | } 62 | -------------------------------------------------------------------------------- /ch9/echo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | 12 | "github.com/go-chi/chi/v5" 13 | ) 14 | 15 | type Message struct { 16 | Msg string 17 | } 18 | 19 | func main() { 20 | r := chi.NewRouter() 21 | r.Post("/", func(w http.ResponseWriter, r *http.Request) { 22 | d := json.NewDecoder(r.Body) 23 | m := Message{} 24 | err := d.Decode(&m) 25 | if err != nil { 26 | json.NewEncoder(w).Encode(errors.New("Unable to decode request body")) 27 | return 28 | } 29 | log.Printf("Received message: %v\n", m.Msg) 30 | 31 | json.NewEncoder(w).Encode(m) 32 | }) 33 | r.Get("/health", func(w http.ResponseWriter, r *http.Request) { 34 | log.Printf("Health check called") 35 | w.Write([]byte("OK")) 36 | }) 37 | r.Get("/healthfail", func(w http.ResponseWriter, r *http.Request) { 38 | log.Printf("Health check failed") 39 | w.WriteHeader(http.StatusInternalServerError) 40 | w.Write([]byte("Internal server error")) 41 | }) 42 | 43 | srv := &http.Server{ 44 | Addr: "0.0.0.0:7777", 45 | Handler: r, 46 | } 47 | 48 | go func() { 49 | log.Println("Listening on http://localhost:7777") 50 | if err := srv.ListenAndServe(); err != nil { 51 | log.Println(err) 52 | } 53 | }() 54 | 55 | // Setup handler for graceful shutdown 56 | c := make(chan os.Signal, 1) 57 | signal.Notify(c, os.Interrupt, syscall.SIGKILL, syscall.SIGTERM) 58 | <-c 59 | 60 | log.Println("Shutting down") 61 | } 62 | -------------------------------------------------------------------------------- /ch12/go.mod: -------------------------------------------------------------------------------- 1 | module cube 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/boltdb/bolt v1.3.1 7 | github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8 8 | github.com/docker/docker v20.10.6+incompatible 9 | github.com/docker/go-connections v0.4.0 10 | github.com/go-chi/chi/v5 v5.0.4 11 | github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 12 | github.com/google/go-cmp v0.5.4 13 | github.com/google/uuid v1.2.0 14 | github.com/moby/moby v20.10.6+incompatible 15 | ) 16 | 17 | require ( 18 | github.com/Microsoft/go-winio v0.5.0 // indirect 19 | github.com/containerd/containerd v1.5.1 // indirect 20 | github.com/docker/distribution v2.7.1+incompatible // indirect 21 | github.com/docker/go-units v0.4.0 // indirect 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/golang/protobuf v1.4.3 // indirect 24 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 25 | github.com/morikuni/aec v1.0.0 // indirect 26 | github.com/opencontainers/go-digest v1.0.0 // indirect 27 | github.com/opencontainers/image-spec v1.0.1 // indirect 28 | github.com/pkg/errors v0.9.1 // indirect 29 | github.com/sirupsen/logrus v1.8.1 // indirect 30 | github.com/spf13/cobra v1.7.0 // indirect 31 | github.com/spf13/pflag v1.0.5 // indirect 32 | golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect 33 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect 34 | google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect 35 | google.golang.org/grpc v1.37.1 // indirect 36 | google.golang.org/protobuf v1.25.0 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /ch12/cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 NAME HERE 3 | 4 | */ 5 | package cmd 6 | 7 | import ( 8 | "os" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | 14 | 15 | // rootCmd represents the base command when called without any subcommands 16 | var rootCmd = &cobra.Command{ 17 | Use: "cube", 18 | Short: "A brief description of your application", 19 | Long: `A longer description that spans multiple lines and likely contains 20 | examples and usage of using your application. For example: 21 | 22 | Cobra is a CLI library for Go that empowers applications. 23 | This application is a tool to generate the needed files 24 | to quickly create a Cobra application.`, 25 | // Uncomment the following line if your bare application 26 | // has an action associated with it: 27 | // Run: func(cmd *cobra.Command, args []string) { }, 28 | } 29 | 30 | // Execute adds all child commands to the root command and sets flags appropriately. 31 | // This is called by main.main(). It only needs to happen once to the rootCmd. 32 | func Execute() { 33 | err := rootCmd.Execute() 34 | if err != nil { 35 | os.Exit(1) 36 | } 37 | } 38 | 39 | func init() { 40 | // Here you will define your flags and configuration settings. 41 | // Cobra supports persistent flags, which, if defined here, 42 | // will be global for your application. 43 | 44 | // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cube.yaml)") 45 | 46 | // Cobra also supports local flags, which will only run 47 | // when this action is called directly. 48 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /ch11/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/manager" 5 | "cube/worker" 6 | "fmt" 7 | "os" 8 | "strconv" 9 | ) 10 | 11 | func main() { 12 | whost := os.Getenv("CUBE_WORKER_HOST") 13 | wport, _ := strconv.Atoi(os.Getenv("CUBE_WORKER_PORT")) 14 | 15 | mhost := os.Getenv("CUBE_MANAGER_HOST") 16 | mport, _ := strconv.Atoi(os.Getenv("CUBE_MANAGER_PORT")) 17 | 18 | fmt.Println("Starting Cube worker") 19 | 20 | w1 := worker.New("worker-1", "persistent") 21 | // w1 := worker.New("worker-1", "memory") 22 | wapi1 := worker.Api{Address: whost, Port: wport, Worker: w1} 23 | 24 | w2 := worker.New("worker-2", "persistent") 25 | // w2 := worker.New("worker-2", "memory") 26 | wapi2 := worker.Api{Address: whost, Port: wport + 1, Worker: w2} 27 | 28 | w3 := worker.New("worker-3", "persistent") 29 | // w3 := worker.New("worker-3", "memory") 30 | wapi3 := worker.Api{Address: whost, Port: wport + 2, Worker: w3} 31 | 32 | go w1.RunTasks() 33 | go w1.UpdateTasks() 34 | go wapi1.Start() 35 | 36 | go w2.RunTasks() 37 | go w2.UpdateTasks() 38 | go wapi2.Start() 39 | 40 | go w3.RunTasks() 41 | go w3.UpdateTasks() 42 | go wapi3.Start() 43 | 44 | fmt.Println("Starting Cube manager") 45 | 46 | workers := []string{ 47 | fmt.Sprintf("%s:%d", whost, wport), 48 | fmt.Sprintf("%s:%d", whost, wport+1), 49 | fmt.Sprintf("%s:%d", whost, wport+2), 50 | } 51 | // m := manager.New(workers, "roundrobin") 52 | // m := manager.New(workers, "epvm", "memory") 53 | m := manager.New(workers, "epvm", "persistent") 54 | mapi := manager.Api{Address: mhost, Port: mport, Manager: m} 55 | 56 | go m.ProcessTasks() 57 | go m.UpdateTasks() 58 | go m.DoHealthChecks() 59 | //go m.UpdateNodeStats() 60 | 61 | mapi.Start() 62 | 63 | } 64 | -------------------------------------------------------------------------------- /ch10/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/manager" 5 | "cube/task" 6 | "cube/worker" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/golang-collections/collections/queue" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func main() { 16 | whost := os.Getenv("CUBE_WORKER_HOST") 17 | wport, _ := strconv.Atoi(os.Getenv("CUBE_WORKER_PORT")) 18 | 19 | mhost := os.Getenv("CUBE_MANAGER_HOST") 20 | mport, _ := strconv.Atoi(os.Getenv("CUBE_MANAGER_PORT")) 21 | 22 | fmt.Println("Starting Cube worker") 23 | 24 | w1 := worker.Worker{ 25 | Queue: *queue.New(), 26 | Db: make(map[uuid.UUID]*task.Task), 27 | } 28 | wapi1 := worker.Api{Address: whost, Port: wport, Worker: &w1} 29 | 30 | w2 := worker.Worker{ 31 | Queue: *queue.New(), 32 | Db: make(map[uuid.UUID]*task.Task), 33 | } 34 | wapi2 := worker.Api{Address: whost, Port: wport + 1, Worker: &w2} 35 | 36 | w3 := worker.Worker{ 37 | Queue: *queue.New(), 38 | Db: make(map[uuid.UUID]*task.Task), 39 | } 40 | wapi3 := worker.Api{Address: whost, Port: wport + 2, Worker: &w3} 41 | 42 | go w1.RunTasks() 43 | go w1.UpdateTasks() 44 | go wapi1.Start() 45 | 46 | go w2.RunTasks() 47 | go w2.UpdateTasks() 48 | go wapi2.Start() 49 | 50 | go w3.RunTasks() 51 | go w3.UpdateTasks() 52 | go wapi3.Start() 53 | 54 | fmt.Println("Starting Cube manager") 55 | 56 | workers := []string{ 57 | fmt.Sprintf("%s:%d", whost, wport), 58 | fmt.Sprintf("%s:%d", whost, wport+1), 59 | fmt.Sprintf("%s:%d", whost, wport+2), 60 | } 61 | //m := manager.New(workers, "roundrobin") 62 | m := manager.New(workers, "epvm") 63 | mapi := manager.Api{Address: mhost, Port: mport, Manager: m} 64 | 65 | go m.ProcessTasks() 66 | go m.UpdateTasks() 67 | go m.DoHealthChecks() 68 | //go m.UpdateNodeStats() 69 | 70 | mapi.Start() 71 | 72 | } 73 | -------------------------------------------------------------------------------- /ch12/cmd/manager.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cube/manager" 5 | "log" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | rootCmd.AddCommand(managerCmd) 12 | managerCmd.Flags().StringP("host", "H", "0.0.0.0", "Hostname or IP address") 13 | managerCmd.Flags().IntP("port", "p", 5555, "Port on which to listen") 14 | managerCmd.Flags().StringSliceP("workers", "w", []string{"localhost:5556"}, "List of workers on which the manager will schedule tasks.") 15 | managerCmd.Flags().StringP("scheduler", "s", "epvm", "Name of scheduler to use.") 16 | managerCmd.Flags().StringP("dbType", "d", "memory", "Type of datastore to use for events and tasks (\"memory\" or \"persistent\")") 17 | } 18 | 19 | var managerCmd = &cobra.Command{ 20 | Use: "manager", 21 | Short: "Manager command to operate a Cube manager node.", 22 | Long: `cube manager command. 23 | 24 | The manager controls the orchestration system and is responsible for: 25 | - Accepting tasks from users 26 | - Scheduling tasks onto worker nodes 27 | - Rescheduling tasks in the event of a node failure 28 | - Periodically polling workers to get task updates`, 29 | Run: func(cmd *cobra.Command, args []string) { 30 | host, _ := cmd.Flags().GetString("host") 31 | port, _ := cmd.Flags().GetInt("port") 32 | workers, _ := cmd.Flags().GetStringSlice("workers") 33 | scheduler, _ := cmd.Flags().GetString("scheduler") 34 | dbType, _ := cmd.Flags().GetString("dbType") 35 | 36 | log.Println("Starting manager.") 37 | m := manager.New(workers, scheduler, dbType) 38 | api := manager.Api{Address: host, Port: port, Manager: m} 39 | go m.ProcessTasks() 40 | go m.UpdateTasks() 41 | go m.DoHealthChecks() 42 | go m.UpdateNodeStats() 43 | log.Printf("Starting manager API on http://%s:%d", host, port) 44 | api.Start() 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /ch5/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/google/uuid" 12 | ) 13 | 14 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 15 | d := json.NewDecoder(r.Body) 16 | d.DisallowUnknownFields() 17 | 18 | te := task.TaskEvent{} 19 | err := d.Decode(&te) 20 | if err != nil { 21 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 22 | log.Printf(msg) 23 | w.WriteHeader(400) 24 | e := ErrResponse{ 25 | HTTPStatusCode: 400, 26 | Message: msg, 27 | } 28 | json.NewEncoder(w).Encode(e) 29 | return 30 | } 31 | 32 | a.Worker.AddTask(te.Task) 33 | log.Printf("Added task %v\n", te.Task.ID) 34 | w.WriteHeader(201) 35 | json.NewEncoder(w).Encode(te.Task) 36 | } 37 | 38 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 39 | w.Header().Set("Content-Type", "application/json") 40 | w.WriteHeader(200) 41 | json.NewEncoder(w).Encode(a.Worker.GetTasks()) 42 | } 43 | 44 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 45 | taskID := chi.URLParam(r, "taskID") 46 | if taskID == "" { 47 | log.Printf("No taskID passed in request.\n") 48 | w.WriteHeader(400) 49 | } 50 | 51 | tID, _ := uuid.Parse(taskID) 52 | _, ok := a.Worker.Db[tID] 53 | if !ok { 54 | log.Printf("No task with ID %v found", tID) 55 | w.WriteHeader(404) 56 | } 57 | 58 | taskToStop := a.Worker.Db[tID] 59 | // we need to make a copy so we are not modifying the task in the datastore 60 | taskCopy := *taskToStop 61 | taskCopy.State = task.Completed 62 | a.Worker.AddTask(taskCopy) 63 | 64 | log.Printf("Added task %v to stop container %v\n", taskToStop.ID, taskToStop.ContainerID) 65 | w.WriteHeader(204) 66 | } 67 | -------------------------------------------------------------------------------- /ch10/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "cube/stats" 5 | "cube/utils" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | ) 13 | 14 | type Node struct { 15 | Name string 16 | Ip string 17 | Api string 18 | Memory int64 19 | MemoryAllocated int64 20 | Disk int64 21 | DiskAllocated int64 22 | Stats stats.Stats 23 | Role string 24 | TaskCount int 25 | } 26 | 27 | func NewNode(name string, api string, role string) *Node { 28 | return &Node{ 29 | Name: name, 30 | Api: api, 31 | Role: role, 32 | } 33 | } 34 | 35 | func (n *Node) GetStats() (*stats.Stats, error) { 36 | var resp *http.Response 37 | var err error 38 | 39 | url := fmt.Sprintf("%s/stats", n.Api) 40 | resp, err = utils.HTTPWithRetry(http.Get, url) 41 | if err != nil { 42 | msg := fmt.Sprintf("Unable to connect to %v. Permanent failure.\n", n.Api) 43 | log.Println(msg) 44 | return nil, errors.New(msg) 45 | } 46 | 47 | if resp.StatusCode != 200 { 48 | msg := fmt.Sprintf("Error retrieving stats from %v: %v", n.Api, err) 49 | log.Println(msg) 50 | return nil, errors.New(msg) 51 | } 52 | 53 | defer resp.Body.Close() 54 | body, _ := ioutil.ReadAll(resp.Body) 55 | var stats stats.Stats 56 | err = json.Unmarshal(body, &stats) 57 | if err != nil { 58 | msg := fmt.Sprintf("error decoding message while getting stats for node %s", n.Name) 59 | log.Println(msg) 60 | return nil, errors.New(msg) 61 | } 62 | 63 | if stats.MemStats == nil || stats.DiskStats == nil { 64 | return nil, fmt.Errorf("error getting stats from node %s", n.Name) 65 | } 66 | 67 | n.Memory = int64(stats.MemTotalKb()) 68 | n.Disk = int64(stats.DiskTotal()) 69 | n.Stats = stats 70 | 71 | return &n.Stats, nil 72 | } 73 | -------------------------------------------------------------------------------- /ch11/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "cube/stats" 5 | "cube/utils" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | ) 13 | 14 | type Node struct { 15 | Name string 16 | Ip string 17 | Api string 18 | Memory int64 19 | MemoryAllocated int64 20 | Disk int64 21 | DiskAllocated int64 22 | Stats stats.Stats 23 | Role string 24 | TaskCount int 25 | } 26 | 27 | func NewNode(name string, api string, role string) *Node { 28 | return &Node{ 29 | Name: name, 30 | Api: api, 31 | Role: role, 32 | } 33 | } 34 | 35 | func (n *Node) GetStats() (*stats.Stats, error) { 36 | var resp *http.Response 37 | var err error 38 | 39 | url := fmt.Sprintf("%s/stats", n.Api) 40 | resp, err = utils.HTTPWithRetry(http.Get, url) 41 | if err != nil { 42 | msg := fmt.Sprintf("Unable to connect to %v. Permanent failure.\n", n.Api) 43 | log.Println(msg) 44 | return nil, errors.New(msg) 45 | } 46 | 47 | if resp.StatusCode != 200 { 48 | msg := fmt.Sprintf("Error retrieving stats from %v: %v", n.Api, err) 49 | log.Println(msg) 50 | return nil, errors.New(msg) 51 | } 52 | 53 | defer resp.Body.Close() 54 | body, _ := ioutil.ReadAll(resp.Body) 55 | var stats stats.Stats 56 | err = json.Unmarshal(body, &stats) 57 | if err != nil { 58 | msg := fmt.Sprintf("error decoding message while getting stats for node %s", n.Name) 59 | log.Println(msg) 60 | return nil, errors.New(msg) 61 | } 62 | 63 | if stats.MemStats == nil || stats.DiskStats == nil { 64 | return nil, fmt.Errorf("error getting stats from node %s", n.Name) 65 | } 66 | 67 | n.Memory = int64(stats.MemTotalKb()) 68 | n.Disk = int64(stats.DiskTotal()) 69 | n.Stats = stats 70 | 71 | return &n.Stats, nil 72 | } 73 | -------------------------------------------------------------------------------- /ch12/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "cube/stats" 5 | "cube/utils" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | ) 13 | 14 | type Node struct { 15 | Name string 16 | Ip string 17 | Api string 18 | Memory int64 19 | MemoryAllocated int64 20 | Disk int64 21 | DiskAllocated int64 22 | Stats stats.Stats 23 | Role string 24 | TaskCount int 25 | } 26 | 27 | func NewNode(name string, api string, role string) *Node { 28 | return &Node{ 29 | Name: name, 30 | Api: api, 31 | Role: role, 32 | } 33 | } 34 | 35 | func (n *Node) GetStats() (*stats.Stats, error) { 36 | var resp *http.Response 37 | var err error 38 | 39 | url := fmt.Sprintf("%s/stats", n.Api) 40 | resp, err = utils.HTTPWithRetry(http.Get, url) 41 | if err != nil { 42 | msg := fmt.Sprintf("Unable to connect to %v. Permanent failure.\n", n.Api) 43 | log.Println(msg) 44 | return nil, errors.New(msg) 45 | } 46 | 47 | if resp.StatusCode != 200 { 48 | msg := fmt.Sprintf("Error retrieving stats from %v: %v", n.Api, err) 49 | log.Println(msg) 50 | return nil, errors.New(msg) 51 | } 52 | 53 | defer resp.Body.Close() 54 | body, _ := ioutil.ReadAll(resp.Body) 55 | var stats stats.Stats 56 | err = json.Unmarshal(body, &stats) 57 | if err != nil { 58 | msg := fmt.Sprintf("error decoding message while getting stats for node %s", n.Name) 59 | log.Println(msg) 60 | return nil, errors.New(msg) 61 | } 62 | 63 | if stats.MemStats == nil || stats.DiskStats == nil { 64 | return nil, fmt.Errorf("error getting stats from node %s", n.Name) 65 | } 66 | 67 | n.Memory = int64(stats.MemTotalKb()) 68 | n.Disk = int64(stats.DiskTotal()) 69 | n.Stats = stats 70 | 71 | return &n.Stats, nil 72 | } 73 | -------------------------------------------------------------------------------- /ch12/cmd/status.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "os" 11 | "text/tabwriter" 12 | "time" 13 | 14 | "github.com/docker/go-units" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | func init() { 19 | rootCmd.AddCommand(statusCmd) 20 | statusCmd.Flags().StringP("manager", "m", "localhost:5555", "Manager to talk to") 21 | } 22 | 23 | var statusCmd = &cobra.Command{ 24 | Use: "status", 25 | Short: "Status command to list tasks.", 26 | Long: `cube status command. 27 | 28 | The status command allows a user to get the status of tasks from the Cube manager.`, 29 | Run: func(cmd *cobra.Command, args []string) { 30 | manager, _ := cmd.Flags().GetString("manager") 31 | 32 | url := fmt.Sprintf("http://%s/tasks", manager) 33 | resp, _ := http.Get(url) 34 | body, err := io.ReadAll(resp.Body) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | defer resp.Body.Close() 39 | 40 | var tasks []*task.Task 41 | err = json.Unmarshal(body, &tasks) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', tabwriter.TabIndent) 47 | fmt.Fprintln(w, "ID\tNAME\tCREATED\tSTATE\tCONTAINERNAME\tIMAGE\t") 48 | for _, task := range tasks { 49 | var start string 50 | if task.StartTime.IsZero() { 51 | start = fmt.Sprintf("%s ago", units.HumanDuration(time.Now().UTC().Sub(time.Now().UTC()))) 52 | } else { 53 | start = fmt.Sprintf("%s ago", units.HumanDuration(time.Now().UTC().Sub(task.StartTime))) 54 | } 55 | 56 | // TODO: there is a bug here, state for stopped jobs is showing as Running 57 | state := task.State.String()[task.State] 58 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n", task.ID, task.Name, start, state, task.Name, task.Image) 59 | } 60 | w.Flush() 61 | }, 62 | } 63 | -------------------------------------------------------------------------------- /ch12/cmd/run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/fs" 8 | "log" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func init() { 17 | rootCmd.AddCommand(runCmd) 18 | runCmd.Flags().StringP("manager", "m", "localhost:5555", "Manager to talk to") 19 | runCmd.Flags().StringP("filename", "f", "task.json", "Task specification file") 20 | } 21 | 22 | func fileExists(filename string) bool { 23 | _, err := os.Stat(filename) 24 | 25 | return !errors.Is(err, fs.ErrNotExist) 26 | } 27 | 28 | var runCmd = &cobra.Command{ 29 | Use: "run", 30 | Short: "Run a new task.", 31 | Long: `cube run command. 32 | 33 | The run command starts a new task.`, 34 | Run: func(cmd *cobra.Command, args []string) { 35 | manager, _ := cmd.Flags().GetString("manager") 36 | // TODO add check to verify file exists and exit if not 37 | filename, _ := cmd.Flags().GetString("filename") 38 | 39 | fullFilePath, err := filepath.Abs(filename) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | if !fileExists(fullFilePath) { 45 | log.Fatalf("File %s does not exist.", filename) 46 | } 47 | 48 | log.Printf("Using manager: %v\n", manager) 49 | log.Printf("Using file: %v\n", fullFilePath) 50 | 51 | data, err := os.ReadFile(filename) 52 | if err != nil { 53 | log.Fatalf("Unable to read file: %v", filename) 54 | } 55 | log.Printf("Data: %v\n", string(data)) 56 | 57 | url := fmt.Sprintf("http://%s/tasks", manager) 58 | resp, err := http.Post(url, "application/json", bytes.NewBuffer(data)) 59 | if err != nil { 60 | log.Panic(err) 61 | } 62 | 63 | if resp.StatusCode != http.StatusCreated { 64 | log.Printf("Error sending request: %v", resp.StatusCode) 65 | } 66 | 67 | defer resp.Body.Close() 68 | log.Println("Successfully sent task request to manager") 69 | }, 70 | } 71 | -------------------------------------------------------------------------------- /ch8/manager/handlers.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 16 | d := json.NewDecoder(r.Body) 17 | 18 | te := task.TaskEvent{} 19 | err := d.Decode(&te) 20 | if err != nil { 21 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 22 | log.Printf(msg) 23 | w.WriteHeader(400) 24 | e := ErrResponse{ 25 | HTTPStatusCode: 400, 26 | Message: msg, 27 | } 28 | json.NewEncoder(w).Encode(e) 29 | return 30 | } 31 | 32 | a.Manager.AddTask(te) 33 | log.Printf("Added task %v\n", te.Task.ID) 34 | w.WriteHeader(201) 35 | json.NewEncoder(w).Encode(te.Task) 36 | } 37 | 38 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 39 | w.Header().Set("Content-Type", "application/json") 40 | w.WriteHeader(200) 41 | json.NewEncoder(w).Encode(a.Manager.GetTasks()) 42 | } 43 | 44 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 45 | taskID := chi.URLParam(r, "taskID") 46 | if taskID == "" { 47 | log.Printf("No taskID passed in request.\n") 48 | w.WriteHeader(400) 49 | } 50 | 51 | tID, _ := uuid.Parse(taskID) 52 | taskToStop, ok := a.Manager.TaskDb[tID] 53 | if !ok { 54 | log.Printf("No task with ID %v found", tID) 55 | w.WriteHeader(404) 56 | } 57 | 58 | te := task.TaskEvent{ 59 | ID: uuid.New(), 60 | State: task.Completed, 61 | Timestamp: time.Now(), 62 | } 63 | // we need to make a copy so we are not modifying the task in the datastore 64 | taskCopy := *taskToStop 65 | taskCopy.State = task.Completed 66 | te.Task = taskCopy 67 | a.Manager.AddTask(te) 68 | 69 | log.Printf("Added task event %v to stop task %v\n", te.ID, taskToStop.ID) 70 | w.WriteHeader(204) 71 | } 72 | -------------------------------------------------------------------------------- /ch7/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/manager" 5 | "cube/task" 6 | "cube/worker" 7 | "fmt" 8 | "log" 9 | "os" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/golang-collections/collections/queue" 14 | "github.com/google/uuid" 15 | ) 16 | 17 | func main() { 18 | host := os.Getenv("CUBE_HOST") 19 | port, _ := strconv.Atoi(os.Getenv("CUBE_PORT")) 20 | 21 | fmt.Println("Starting Cube worker") 22 | 23 | w := worker.Worker{ 24 | Queue: *queue.New(), 25 | Db: make(map[uuid.UUID]*task.Task), 26 | } 27 | api := worker.Api{Address: host, Port: port, Worker: &w} 28 | 29 | go runTasks(&w) 30 | go w.CollectStats() 31 | go api.Start() 32 | 33 | workers := []string{fmt.Sprintf("%s:%d", host, port)} 34 | m := manager.New(workers) 35 | 36 | for i := 0; i < 3; i++ { 37 | t := task.Task{ 38 | ID: uuid.New(), 39 | Name: fmt.Sprintf("test-container-%d", i), 40 | State: task.Scheduled, 41 | Image: "strm/helloworld-http", 42 | } 43 | te := task.TaskEvent{ 44 | ID: uuid.New(), 45 | State: task.Running, 46 | Task: t, 47 | } 48 | m.AddTask(te) 49 | m.SendWork() 50 | } 51 | 52 | go func() { 53 | for { 54 | fmt.Printf("[Manager] Updating tasks from %d workers\n", len(m.Workers)) 55 | m.UpdateTasks() 56 | time.Sleep(15 * time.Second) 57 | } 58 | }() 59 | 60 | for { 61 | for _, t := range m.TaskDb { 62 | fmt.Printf("[Manager] Task: id: %s, state: %d\n", t.ID, t.State) 63 | time.Sleep(15 * time.Second) 64 | } 65 | } 66 | 67 | } 68 | 69 | func runTasks(w *worker.Worker) { 70 | for { 71 | if w.Queue.Len() != 0 { 72 | result := w.RunTask() 73 | if result.Error != nil { 74 | log.Printf("Error running task: %v\n", result.Error) 75 | } 76 | } else { 77 | log.Printf("No tasks to process currently.\n") 78 | } 79 | log.Println("Sleeping for 10 seconds.") 80 | time.Sleep(10 * time.Second) 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /ch9/manager/handlers.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 16 | d := json.NewDecoder(r.Body) 17 | d.DisallowUnknownFields() 18 | 19 | te := task.TaskEvent{} 20 | err := d.Decode(&te) 21 | if err != nil { 22 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 23 | log.Printf(msg) 24 | w.WriteHeader(400) 25 | e := ErrResponse{ 26 | HTTPStatusCode: 400, 27 | Message: msg, 28 | } 29 | json.NewEncoder(w).Encode(e) 30 | return 31 | } 32 | 33 | a.Manager.AddTask(te) 34 | log.Printf("Added task %v\n", te.Task.ID) 35 | w.WriteHeader(201) 36 | json.NewEncoder(w).Encode(te.Task) 37 | } 38 | 39 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 40 | w.Header().Set("Content-Type", "application/json") 41 | w.WriteHeader(200) 42 | json.NewEncoder(w).Encode(a.Manager.GetTasks()) 43 | } 44 | 45 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 46 | taskID := chi.URLParam(r, "taskID") 47 | if taskID == "" { 48 | log.Printf("No taskID passed in request.\n") 49 | w.WriteHeader(400) 50 | } 51 | 52 | tID, _ := uuid.Parse(taskID) 53 | _, ok := a.Manager.TaskDb[tID] 54 | if !ok { 55 | log.Printf("No task with ID %v found", tID) 56 | w.WriteHeader(404) 57 | } 58 | 59 | te := task.TaskEvent{ 60 | ID: uuid.New(), 61 | State: task.Completed, 62 | Timestamp: time.Now(), 63 | } 64 | taskToStop := a.Manager.TaskDb[tID] 65 | // we need to make a copy so we are not modifying the task in the datastore 66 | taskCopy := *taskToStop 67 | taskCopy.State = task.Completed 68 | te.Task = taskCopy 69 | a.Manager.AddTask(te) 70 | 71 | log.Printf("Added task event %v to stop task %v\n", te.ID, taskToStop.ID) 72 | w.WriteHeader(204) 73 | } 74 | -------------------------------------------------------------------------------- /ch10/manager/handlers.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 16 | d := json.NewDecoder(r.Body) 17 | d.DisallowUnknownFields() 18 | 19 | te := task.TaskEvent{} 20 | err := d.Decode(&te) 21 | if err != nil { 22 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 23 | log.Printf(msg) 24 | w.WriteHeader(400) 25 | e := ErrResponse{ 26 | HTTPStatusCode: 400, 27 | Message: msg, 28 | } 29 | json.NewEncoder(w).Encode(e) 30 | return 31 | } 32 | 33 | a.Manager.AddTask(te) 34 | log.Printf("Added task %v\n", te.Task.ID) 35 | w.WriteHeader(201) 36 | json.NewEncoder(w).Encode(te.Task) 37 | } 38 | 39 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 40 | w.Header().Set("Content-Type", "application/json") 41 | w.WriteHeader(200) 42 | json.NewEncoder(w).Encode(a.Manager.GetTasks()) 43 | } 44 | 45 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 46 | taskID := chi.URLParam(r, "taskID") 47 | if taskID == "" { 48 | log.Printf("No taskID passed in request.\n") 49 | w.WriteHeader(400) 50 | } 51 | 52 | tID, _ := uuid.Parse(taskID) 53 | _, ok := a.Manager.TaskDb[tID] 54 | if !ok { 55 | log.Printf("No task with ID %v found", tID) 56 | w.WriteHeader(404) 57 | } 58 | 59 | te := task.TaskEvent{ 60 | ID: uuid.New(), 61 | State: task.Completed, 62 | Timestamp: time.Now(), 63 | } 64 | taskToStop := a.Manager.TaskDb[tID] 65 | // we need to make a copy so we are not modifying the task in the datastore 66 | taskCopy := *taskToStop 67 | //taskCopy.State = task.Completed 68 | te.Task = taskCopy 69 | a.Manager.AddTask(te) 70 | 71 | log.Printf("Added task event %v to stop task %v\n", te.ID, taskToStop.ID) 72 | w.WriteHeader(204) 73 | } 74 | -------------------------------------------------------------------------------- /ch11/manager/handlers.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 16 | d := json.NewDecoder(r.Body) 17 | d.DisallowUnknownFields() 18 | 19 | te := task.TaskEvent{} 20 | err := d.Decode(&te) 21 | if err != nil { 22 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 23 | log.Println(msg) 24 | w.WriteHeader(400) 25 | e := ErrResponse{ 26 | HTTPStatusCode: 400, 27 | Message: msg, 28 | } 29 | json.NewEncoder(w).Encode(e) 30 | return 31 | } 32 | 33 | a.Manager.AddTask(te) 34 | log.Printf("Added task %v\n", te.Task.ID) 35 | w.WriteHeader(201) 36 | json.NewEncoder(w).Encode(te.Task) 37 | } 38 | 39 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 40 | w.Header().Set("Content-Type", "application/json") 41 | w.WriteHeader(200) 42 | json.NewEncoder(w).Encode(a.Manager.GetTasks()) 43 | } 44 | 45 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 46 | taskID := chi.URLParam(r, "taskID") 47 | if taskID == "" { 48 | log.Printf("No taskID passed in request.\n") 49 | w.WriteHeader(400) 50 | } 51 | 52 | tID, _ := uuid.Parse(taskID) 53 | taskToStop, err := a.Manager.TaskDb.Get(tID.String()) 54 | if err != nil { 55 | log.Printf("No task with ID %v found", tID) 56 | w.WriteHeader(404) 57 | return 58 | } 59 | 60 | te := task.TaskEvent{ 61 | ID: uuid.New(), 62 | State: task.Completed, 63 | Timestamp: time.Now(), 64 | } 65 | // we need to make a copy so we are not modifying the task in the datastore 66 | taskCopy := taskToStop.(*task.Task) 67 | //taskCopy.State = task.Completed 68 | te.Task = *taskCopy 69 | a.Manager.AddTask(te) 70 | 71 | log.Printf("Added task event %v to stop task %v\n", te.ID, taskCopy.ID) 72 | w.WriteHeader(204) 73 | } 74 | -------------------------------------------------------------------------------- /ch7/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/google/uuid" 12 | ) 13 | 14 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 15 | d := json.NewDecoder(r.Body) 16 | 17 | te := task.TaskEvent{} 18 | err := d.Decode(&te) 19 | if err != nil { 20 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 21 | log.Printf(msg) 22 | w.WriteHeader(400) 23 | e := ErrResponse{ 24 | HTTPStatusCode: 400, 25 | Message: msg, 26 | } 27 | json.NewEncoder(w).Encode(e) 28 | return 29 | } 30 | 31 | a.Worker.AddTask(te.Task) 32 | log.Printf("Added task %v\n", te.Task.ID) 33 | w.WriteHeader(201) 34 | json.NewEncoder(w).Encode(te.Task) 35 | } 36 | 37 | func (a *Api) GetStatsHandler(w http.ResponseWriter, r *http.Request) { 38 | w.Header().Set("Content-Type", "application/json") 39 | w.WriteHeader(200) 40 | json.NewEncoder(w).Encode(a.Worker.Stats) 41 | } 42 | 43 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 44 | w.Header().Set("Content-Type", "application/json") 45 | w.WriteHeader(200) 46 | json.NewEncoder(w).Encode(a.Worker.GetTasks()) 47 | } 48 | 49 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 50 | taskID := chi.URLParam(r, "taskID") 51 | if taskID == "" { 52 | log.Printf("No taskID passed in request.\n") 53 | w.WriteHeader(400) 54 | } 55 | 56 | tID, _ := uuid.Parse(taskID) 57 | _, ok := a.Worker.Db[tID] 58 | if !ok { 59 | log.Printf("No task with ID %v found", tID) 60 | w.WriteHeader(404) 61 | } 62 | 63 | taskToStop := a.Worker.Db[tID] 64 | // we need to make a copy so we are not modifying the task in the datastore 65 | taskCopy := *taskToStop 66 | taskCopy.State = task.Completed 67 | a.Worker.AddTask(taskCopy) 68 | 69 | log.Printf("Added task %v to stop container %v\n", taskToStop.ID, taskToStop.ContainerID) 70 | w.WriteHeader(204) 71 | } 72 | -------------------------------------------------------------------------------- /ch8/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/google/uuid" 12 | ) 13 | 14 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 15 | d := json.NewDecoder(r.Body) 16 | 17 | te := task.TaskEvent{} 18 | err := d.Decode(&te) 19 | if err != nil { 20 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 21 | log.Printf(msg) 22 | w.WriteHeader(400) 23 | e := ErrResponse{ 24 | HTTPStatusCode: 400, 25 | Message: msg, 26 | } 27 | json.NewEncoder(w).Encode(e) 28 | return 29 | } 30 | 31 | a.Worker.AddTask(te.Task) 32 | log.Printf("Added task %v\n", te.Task.ID) 33 | w.WriteHeader(201) 34 | json.NewEncoder(w).Encode(te.Task) 35 | } 36 | 37 | func (a *Api) GetStatsHandler(w http.ResponseWriter, r *http.Request) { 38 | w.Header().Set("Content-Type", "application/json") 39 | w.WriteHeader(200) 40 | json.NewEncoder(w).Encode(a.Worker.Stats) 41 | } 42 | 43 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 44 | w.Header().Set("Content-Type", "application/json") 45 | w.WriteHeader(200) 46 | json.NewEncoder(w).Encode(a.Worker.GetTasks()) 47 | } 48 | 49 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 50 | taskID := chi.URLParam(r, "taskID") 51 | if taskID == "" { 52 | log.Printf("No taskID passed in request.\n") 53 | w.WriteHeader(400) 54 | } 55 | 56 | tID, _ := uuid.Parse(taskID) 57 | _, ok := a.Worker.Db[tID] 58 | if !ok { 59 | log.Printf("No task with ID %v found", tID) 60 | w.WriteHeader(404) 61 | } 62 | 63 | taskToStop := a.Worker.Db[tID] 64 | // we need to make a copy so we are not modifying the task in the datastore 65 | taskCopy := *taskToStop 66 | taskCopy.State = task.Completed 67 | a.Worker.AddTask(taskCopy) 68 | 69 | log.Printf("Added task %v to stop container %v\n", taskToStop.ID, taskToStop.ContainerID) 70 | w.WriteHeader(204) 71 | } 72 | -------------------------------------------------------------------------------- /ch6/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/google/uuid" 12 | ) 13 | 14 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 15 | d := json.NewDecoder(r.Body) 16 | d.DisallowUnknownFields() 17 | 18 | te := task.TaskEvent{} 19 | err := d.Decode(&te) 20 | if err != nil { 21 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 22 | log.Printf(msg) 23 | w.WriteHeader(400) 24 | e := ErrResponse{ 25 | HTTPStatusCode: 400, 26 | Message: msg, 27 | } 28 | json.NewEncoder(w).Encode(e) 29 | return 30 | } 31 | 32 | a.Worker.AddTask(te.Task) 33 | log.Printf("Added task %v\n", te.Task.ID) 34 | w.WriteHeader(201) 35 | json.NewEncoder(w).Encode(te.Task) 36 | } 37 | 38 | func (a *Api) GetStatsHandler(w http.ResponseWriter, r *http.Request) { 39 | w.Header().Set("Content-Type", "application/json") 40 | w.WriteHeader(200) 41 | json.NewEncoder(w).Encode(a.Worker.Stats) 42 | } 43 | 44 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 45 | w.Header().Set("Content-Type", "application/json") 46 | w.WriteHeader(200) 47 | json.NewEncoder(w).Encode(a.Worker.GetTasks()) 48 | } 49 | 50 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 51 | taskID := chi.URLParam(r, "taskID") 52 | if taskID == "" { 53 | log.Printf("No taskID passed in request.\n") 54 | w.WriteHeader(400) 55 | } 56 | 57 | tID, _ := uuid.Parse(taskID) 58 | _, ok := a.Worker.Db[tID] 59 | if !ok { 60 | log.Printf("No task with ID %v found", tID) 61 | w.WriteHeader(404) 62 | } 63 | 64 | taskToStop := a.Worker.Db[tID] 65 | // we need to make a copy so we are not modifying the task in the datastore 66 | taskCopy := *taskToStop 67 | taskCopy.State = task.Completed 68 | a.Worker.AddTask(taskCopy) 69 | 70 | log.Printf("Added task %v to stop container %v\n", taskToStop.ID, taskToStop.ContainerID) 71 | w.WriteHeader(204) 72 | } 73 | -------------------------------------------------------------------------------- /ch3/task/state_machine.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | type State int 4 | 5 | const ( 6 | Pending State = iota 7 | Scheduled 8 | Running 9 | Completed 10 | Failed 11 | ) 12 | 13 | //type StateMachine struct { 14 | // StartState *State 15 | // CurrentState *State 16 | //} 17 | // 18 | //func (s *StateMachine) GetCurrentState() State { 19 | // return *s.CurrentState 20 | //} 21 | // 22 | //func (s *StateMachine) SetPending() error { 23 | // if s.CurrentState == nil { 24 | // s.CurrentState = *Pending 25 | // } 26 | //} 27 | // 28 | //func (s *StateMachine) SetScheduled(currentState State) error { 29 | // if currentState == Pending { 30 | // s.CurrentState = Scheduled 31 | // return nil 32 | // } 33 | // 34 | // return fmt.Errorf("cannot transition from %s to Scheduled", currentState) 35 | //} 36 | // 37 | //func (s *StateMachine) SetRunning(currentState State) error { 38 | // if Contains(stateTransitionMap[currentState], Running) { 39 | // s.CurrentState = Running 40 | // return nil 41 | // } 42 | // 43 | // return fmt.Errorf("cannot transition from %s to Running", currentState) 44 | //} 45 | // 46 | //func (s *StateMachine) SetCompleted(currentState State) error { 47 | // if Contains(stateTransitionMap[currentState], Completed) { 48 | // s.CurrentState = Completed 49 | // return nil 50 | // } 51 | // 52 | // return fmt.Errorf("cannot transition from %s to Completed", currentState) 53 | //} 54 | // 55 | //func (s *StateMachine) SetFailed(currentState State) error { 56 | // if Contains(stateTransitionMap[currentState], Completed) { 57 | // s.CurrentState = Failed 58 | // return nil 59 | // } 60 | // 61 | // return fmt.Errorf("cannot transition from %s to Failed", currentState) 62 | //} 63 | 64 | var stateTransitionMap = map[State][]State{ 65 | Pending: []State{Scheduled}, 66 | Scheduled: []State{Running, Failed}, 67 | Running: []State{Running, Completed, Failed}, 68 | Completed: []State{}, 69 | Failed: []State{}, 70 | } 71 | 72 | func Contains(states []State, state State) bool { 73 | for _, s := range states { 74 | if s == state { 75 | return true 76 | } 77 | } 78 | return false 79 | } 80 | -------------------------------------------------------------------------------- /ch12/manager/handlers.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 16 | d := json.NewDecoder(r.Body) 17 | d.DisallowUnknownFields() 18 | 19 | te := task.TaskEvent{} 20 | err := d.Decode(&te) 21 | if err != nil { 22 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 23 | log.Println(msg) 24 | w.WriteHeader(400) 25 | e := ErrResponse{ 26 | HTTPStatusCode: 400, 27 | Message: msg, 28 | } 29 | json.NewEncoder(w).Encode(e) 30 | return 31 | } 32 | 33 | a.Manager.AddTask(te) 34 | log.Printf("Added task %v\n", te.Task.ID) 35 | w.WriteHeader(201) 36 | json.NewEncoder(w).Encode(te.Task) 37 | } 38 | 39 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 40 | w.Header().Set("Content-Type", "application/json") 41 | w.WriteHeader(200) 42 | json.NewEncoder(w).Encode(a.Manager.GetTasks()) 43 | } 44 | 45 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 46 | taskID := chi.URLParam(r, "taskID") 47 | if taskID == "" { 48 | log.Printf("No taskID passed in request.\n") 49 | w.WriteHeader(400) 50 | } 51 | 52 | tID, _ := uuid.Parse(taskID) 53 | taskToStop, err := a.Manager.TaskDb.Get(tID.String()) 54 | if err != nil { 55 | log.Printf("No task with ID %v found", tID) 56 | w.WriteHeader(404) 57 | return 58 | } 59 | 60 | te := task.TaskEvent{ 61 | ID: uuid.New(), 62 | State: task.Completed, 63 | Timestamp: time.Now(), 64 | } 65 | // we need to make a copy so we are not modifying the task in the datastore 66 | taskCopy := taskToStop.(*task.Task) 67 | //taskCopy.State = task.Completed 68 | te.Task = *taskCopy 69 | a.Manager.AddTask(te) 70 | 71 | log.Printf("Added task event %v to stop task %v\n", te.ID, taskCopy.ID) 72 | w.WriteHeader(204) 73 | } 74 | 75 | func (a *Api) GetNodesHandler(w http.ResponseWriter, r *http.Request) { 76 | w.Header().Set("Content-Type", "application/json") 77 | w.WriteHeader(200) 78 | json.NewEncoder(w).Encode(a.Manager.WorkerNodes) 79 | } 80 | -------------------------------------------------------------------------------- /ch4/worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/golang-collections/collections/queue" 10 | "github.com/google/uuid" 11 | 12 | "cube/task" 13 | ) 14 | 15 | type Worker struct { 16 | Name string 17 | Queue queue.Queue 18 | Db map[uuid.UUID]*task.Task 19 | TaskCount int 20 | } 21 | 22 | func (w *Worker) CollectStats() { 23 | fmt.Println("I will collect stats") 24 | } 25 | 26 | func (w *Worker) AddTask(t task.Task) { 27 | w.Queue.Enqueue(t) 28 | } 29 | 30 | func (w *Worker) RunTask() task.DockerResult { 31 | t := w.Queue.Dequeue() 32 | if t == nil { 33 | log.Println("No tasks in the queue") 34 | return task.DockerResult{Error: nil} 35 | } 36 | 37 | taskQueued := t.(task.Task) 38 | 39 | taskPersisted := w.Db[taskQueued.ID] 40 | if taskPersisted == nil { 41 | taskPersisted = &taskQueued 42 | w.Db[taskQueued.ID] = &taskQueued 43 | } 44 | 45 | var result task.DockerResult 46 | if task.ValidStateTransition(taskPersisted.State, taskQueued.State) { 47 | switch taskQueued.State { 48 | case task.Scheduled: 49 | result = w.StartTask(taskQueued) 50 | case task.Completed: 51 | result = w.StopTask(taskQueued) 52 | default: 53 | result.Error = errors.New("We should not get here") 54 | } 55 | } else { 56 | err := fmt.Errorf("Invalid transition from %v to %v", taskPersisted.State, taskQueued.State) 57 | result.Error = err 58 | return result 59 | } 60 | return result 61 | } 62 | 63 | func (w *Worker) StartTask(t task.Task) task.DockerResult { 64 | t.StartTime = time.Now().UTC() 65 | config := task.NewConfig(&t) 66 | d := task.NewDocker(config) 67 | result := d.Run() 68 | if result.Error != nil { 69 | log.Printf("Err running task %v: %v\n", t.ID, result.Error) 70 | t.State = task.Failed 71 | w.Db[t.ID] = &t 72 | return result 73 | } 74 | 75 | t.ContainerID = result.ContainerId 76 | t.State = task.Running 77 | w.Db[t.ID] = &t 78 | 79 | return result 80 | } 81 | 82 | func (w *Worker) StopTask(t task.Task) task.DockerResult { 83 | config := task.NewConfig(&t) 84 | d := task.NewDocker(config) 85 | 86 | result := d.Stop(t.ContainerID) 87 | if result.Error != nil { 88 | log.Printf("Error stopping container %v: %v\n", t.ContainerID, result.Error) 89 | } 90 | t.FinishTime = time.Now().UTC() 91 | t.State = task.Completed 92 | w.Db[t.ID] = &t 93 | log.Printf("Stopped and removed container %v for task %v\n", t.ContainerID, t.ID) 94 | 95 | return result 96 | } 97 | -------------------------------------------------------------------------------- /ch9/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "cube/task" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/google/uuid" 12 | ) 13 | 14 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 15 | d := json.NewDecoder(r.Body) 16 | 17 | te := task.TaskEvent{} 18 | err := d.Decode(&te) 19 | if err != nil { 20 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 21 | log.Printf(msg) 22 | w.WriteHeader(400) 23 | e := ErrResponse{ 24 | HTTPStatusCode: 400, 25 | Message: msg, 26 | } 27 | json.NewEncoder(w).Encode(e) 28 | return 29 | } 30 | 31 | a.Worker.AddTask(te.Task) 32 | log.Printf("Added task %v\n", te.Task.ID) 33 | w.WriteHeader(201) 34 | json.NewEncoder(w).Encode(te.Task) 35 | } 36 | 37 | func (a *Api) GetStatsHandler(w http.ResponseWriter, r *http.Request) { 38 | w.Header().Set("Content-Type", "application/json") 39 | w.WriteHeader(200) 40 | json.NewEncoder(w).Encode(a.Worker.Stats) 41 | } 42 | 43 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 44 | w.Header().Set("Content-Type", "application/json") 45 | w.WriteHeader(200) 46 | json.NewEncoder(w).Encode(a.Worker.GetTasks()) 47 | } 48 | 49 | func (a *Api) InspectTaskHandler(w http.ResponseWriter, r *http.Request) { 50 | taskID := chi.URLParam(r, "taskID") 51 | if taskID == "" { 52 | log.Printf("No taskID passed in request.\n") 53 | w.WriteHeader(400) 54 | } 55 | 56 | tID, _ := uuid.Parse(taskID) 57 | t, ok := a.Worker.Db[tID] 58 | if !ok { 59 | log.Printf("No task with ID %v found", tID) 60 | w.WriteHeader(404) 61 | return 62 | } 63 | 64 | resp := a.Worker.InspectTask(*t) 65 | 66 | w.Header().Set("Content-Type", "application/json") 67 | w.WriteHeader(200) 68 | json.NewEncoder(w).Encode(resp.Container) 69 | 70 | } 71 | 72 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 73 | taskID := chi.URLParam(r, "taskID") 74 | if taskID == "" { 75 | log.Printf("No taskID passed in request.\n") 76 | w.WriteHeader(400) 77 | } 78 | 79 | tID, _ := uuid.Parse(taskID) 80 | taskToStop, ok := a.Worker.Db[tID] 81 | if !ok { 82 | log.Printf("No task with ID %v found", tID) 83 | w.WriteHeader(404) 84 | } 85 | 86 | // we need to make a copy so we are not modifying the task in the datastore 87 | taskCopy := *taskToStop 88 | taskCopy.State = task.Completed 89 | a.Worker.AddTask(taskCopy) 90 | 91 | log.Printf("Added task %v to stop container %v\n", taskToStop.ID, taskToStop.ContainerID) 92 | w.WriteHeader(204) 93 | } 94 | -------------------------------------------------------------------------------- /ch3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cube/node" 5 | "cube/task" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/docker/docker/client" 10 | "github.com/golang-collections/collections/queue" 11 | "github.com/google/uuid" 12 | 13 | "cube/manager" 14 | "cube/worker" 15 | ) 16 | 17 | func createContainer() (*task.Docker, *task.DockerResult) { 18 | c := task.Config{ 19 | Name: "test-container-1", 20 | Image: "postgres:13", 21 | Env: []string{ 22 | "POSTGRES_USER=cube", 23 | "POSTGRES_PASSWORD=secret", 24 | }, 25 | } 26 | 27 | dc, _ := client.NewClientWithOpts(client.FromEnv) 28 | d := task.Docker{ 29 | Client: dc, 30 | Config: c, 31 | } 32 | 33 | result := d.Run() 34 | if result.Error != nil { 35 | fmt.Printf("%v\n", result.Error) 36 | return nil, nil 37 | } 38 | 39 | fmt.Printf("Container %s is running with config %v\n", result.ContainerId, c) 40 | return &d, &result 41 | } 42 | 43 | func stopContainer(d *task.Docker, id string) *task.DockerResult { 44 | result := d.Stop(id) 45 | if result.Error != nil { 46 | fmt.Printf("%v\n", result.Error) 47 | return nil 48 | } 49 | 50 | fmt.Printf("Container %s has been stopped and removed\n", result.ContainerId) 51 | return &result 52 | } 53 | 54 | func main() { 55 | t := task.Task{ 56 | ID: uuid.New(), 57 | Name: "Task-1", 58 | State: task.Pending, 59 | Image: "Image-1", 60 | Memory: 1024, 61 | Disk: 1, 62 | } 63 | 64 | te := task.TaskEvent{ 65 | ID: uuid.New(), 66 | State: task.Pending, 67 | Timestamp: time.Now(), 68 | Task: t, 69 | } 70 | 71 | fmt.Printf("task: %v\n", t) 72 | fmt.Printf("task event: %v\n", te) 73 | 74 | w := worker.Worker{ 75 | Queue: *queue.New(), 76 | Db: make(map[uuid.UUID]task.Task), 77 | } 78 | fmt.Printf("worker: %v\n", w) 79 | w.CollectStats() 80 | w.RunTask() 81 | w.StartTask() 82 | w.StopTask() 83 | 84 | m := manager.Manager{ 85 | Pending: *queue.New(), 86 | TaskDb: make(map[string][]task.Task), 87 | EventDb: make(map[string][]task.TaskEvent), 88 | Workers: []string{w.Name}, 89 | } 90 | 91 | fmt.Printf("manager: %v\n", m) 92 | m.SelectWorker() 93 | m.UpdateTasks() 94 | m.SendWork() 95 | 96 | n := node.Node{ 97 | Name: "Node-1", 98 | Ip: "192.168.1.1", 99 | Cores: 4, 100 | Memory: 1024, 101 | Disk: 25, 102 | Role: "worker", 103 | } 104 | 105 | fmt.Printf("node: %v\n", n) 106 | 107 | fmt.Printf("create a test container\n") 108 | dockerTask, createResult := createContainer() 109 | 110 | time.Sleep(time.Second * 5) 111 | 112 | fmt.Printf("stopping container %s\n", createResult.ContainerId) 113 | _ = stopContainer(dockerTask, createResult.ContainerId) 114 | } 115 | -------------------------------------------------------------------------------- /ch10/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "cube/stats" 5 | "cube/task" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 16 | d := json.NewDecoder(r.Body) 17 | 18 | te := task.TaskEvent{} 19 | err := d.Decode(&te) 20 | if err != nil { 21 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 22 | log.Printf(msg) 23 | w.WriteHeader(400) 24 | e := ErrResponse{ 25 | HTTPStatusCode: 400, 26 | Message: msg, 27 | } 28 | json.NewEncoder(w).Encode(e) 29 | return 30 | } 31 | 32 | a.Worker.AddTask(te.Task) 33 | log.Printf("[worker] Added task %v\n", te.Task.ID) 34 | w.WriteHeader(201) 35 | json.NewEncoder(w).Encode(te.Task) 36 | } 37 | 38 | func (a *Api) GetStatsHandler(w http.ResponseWriter, r *http.Request) { 39 | w.Header().Set("Content-Type", "application/json") 40 | if a.Worker.Stats != nil { 41 | w.WriteHeader(200) 42 | json.NewEncoder(w).Encode(*a.Worker.Stats) 43 | return 44 | } 45 | 46 | w.WriteHeader(200) 47 | stats := stats.GetStats() 48 | json.NewEncoder(w).Encode(stats) 49 | } 50 | 51 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 52 | w.Header().Set("Content-Type", "application/json") 53 | w.WriteHeader(200) 54 | json.NewEncoder(w).Encode(a.Worker.GetTasks()) 55 | } 56 | 57 | func (a *Api) InspectTaskHandler(w http.ResponseWriter, r *http.Request) { 58 | taskID := chi.URLParam(r, "taskID") 59 | if taskID == "" { 60 | log.Printf("No taskID passed in request.\n") 61 | w.WriteHeader(400) 62 | } 63 | 64 | tID, _ := uuid.Parse(taskID) 65 | t, ok := a.Worker.Db[tID] 66 | if !ok { 67 | log.Printf("No task with ID %v found", tID) 68 | w.WriteHeader(404) 69 | return 70 | } 71 | 72 | resp := a.Worker.InspectTask(*t) 73 | 74 | w.Header().Set("Content-Type", "application/json") 75 | w.WriteHeader(200) 76 | json.NewEncoder(w).Encode(resp.Container) 77 | 78 | } 79 | 80 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 81 | taskID := chi.URLParam(r, "taskID") 82 | if taskID == "" { 83 | log.Printf("No taskID passed in request.\n") 84 | w.WriteHeader(400) 85 | } 86 | 87 | tID, _ := uuid.Parse(taskID) 88 | taskToStop, ok := a.Worker.Db[tID] 89 | if !ok { 90 | log.Printf("No task with ID %v found", tID) 91 | w.WriteHeader(404) 92 | } 93 | 94 | // we need to make a copy so we are not modifying the task in the datastore 95 | taskCopy := *taskToStop 96 | taskCopy.State = task.Completed 97 | a.Worker.AddTask(taskCopy) 98 | 99 | log.Printf("Added task %v to stop container %v\n", taskToStop.ID, taskToStop.ContainerID) 100 | w.WriteHeader(204) 101 | } 102 | -------------------------------------------------------------------------------- /ch5/worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/golang-collections/collections/queue" 10 | "github.com/google/uuid" 11 | 12 | "cube/task" 13 | ) 14 | 15 | type Worker struct { 16 | Name string 17 | Queue queue.Queue 18 | Db map[uuid.UUID]*task.Task 19 | TaskCount int 20 | } 21 | 22 | func (w *Worker) GetTasks() []*task.Task { 23 | tasks := []*task.Task{} 24 | for _, t := range w.Db { 25 | tasks = append(tasks, t) 26 | } 27 | return tasks 28 | } 29 | 30 | func (w *Worker) CollectStats() { 31 | fmt.Println("I will collect stats") 32 | } 33 | 34 | func (w *Worker) AddTask(t task.Task) { 35 | w.Queue.Enqueue(t) 36 | } 37 | 38 | func (w *Worker) RunTask() task.DockerResult { 39 | t := w.Queue.Dequeue() 40 | if t == nil { 41 | log.Println("No tasks in the queue") 42 | return task.DockerResult{Error: nil} 43 | } 44 | 45 | taskQueued := t.(task.Task) 46 | fmt.Printf("Found task in queue: %v:\n", taskQueued) 47 | 48 | taskPersisted := w.Db[taskQueued.ID] 49 | if taskPersisted == nil { 50 | taskPersisted = &taskQueued 51 | w.Db[taskQueued.ID] = &taskQueued 52 | } 53 | 54 | var result task.DockerResult 55 | if task.ValidStateTransition(taskPersisted.State, taskQueued.State) { 56 | switch taskQueued.State { 57 | case task.Scheduled: 58 | result = w.StartTask(taskQueued) 59 | case task.Completed: 60 | result = w.StopTask(taskQueued) 61 | default: 62 | fmt.Printf("This is a mistake. taskPersisted: %v, taskQueued: %v\n", taskPersisted, taskQueued) 63 | result.Error = errors.New("We should not get here") 64 | } 65 | } else { 66 | err := fmt.Errorf("Invalid transition from %v to %v", taskPersisted.State, taskQueued.State) 67 | result.Error = err 68 | return result 69 | } 70 | return result 71 | } 72 | 73 | func (w *Worker) StartTask(t task.Task) task.DockerResult { 74 | config := task.NewConfig(&t) 75 | d := task.NewDocker(config) 76 | result := d.Run() 77 | if result.Error != nil { 78 | log.Printf("Err running task %v: %v\n", t.ID, result.Error) 79 | t.State = task.Failed 80 | w.Db[t.ID] = &t 81 | return result 82 | } 83 | 84 | t.ContainerID = result.ContainerId 85 | t.State = task.Running 86 | w.Db[t.ID] = &t 87 | 88 | return result 89 | } 90 | 91 | func (w *Worker) StopTask(t task.Task) task.DockerResult { 92 | config := task.NewConfig(&t) 93 | d := task.NewDocker(config) 94 | 95 | result := d.Stop(t.ContainerID) 96 | if result.Error != nil { 97 | log.Printf("Error stopping container %v: %v\n", t.ContainerID, result.Error) 98 | } 99 | t.FinishTime = time.Now().UTC() 100 | t.State = task.Completed 101 | w.Db[t.ID] = &t 102 | log.Printf("Stopped and removed container %v for task %v\n", t.ContainerID, t.ID) 103 | 104 | return result 105 | } 106 | -------------------------------------------------------------------------------- /ch6/worker/stats.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/c9s/goprocinfo/linux" 7 | ) 8 | 9 | type Stats struct { 10 | MemStats *linux.MemInfo 11 | DiskStats *linux.Disk 12 | CpuStats *linux.CPUStat 13 | LoadStats *linux.LoadAvg 14 | TaskCount int 15 | } 16 | 17 | func (s *Stats) MemUsedKb() uint64 { 18 | return s.MemStats.MemTotal - s.MemStats.MemAvailable 19 | } 20 | 21 | func (s *Stats) MemUsedPercent() uint64 { 22 | return s.MemStats.MemAvailable / s.MemStats.MemTotal 23 | } 24 | 25 | func (s *Stats) MemAvailableKb() uint64 { 26 | return s.MemStats.MemAvailable 27 | } 28 | 29 | func (s *Stats) MemTotalKb() uint64 { 30 | return s.MemStats.MemTotal 31 | } 32 | 33 | func (s *Stats) DiskTotal() uint64 { 34 | return s.DiskStats.All 35 | } 36 | 37 | func (s *Stats) DiskFree() uint64 { 38 | return s.DiskStats.Free 39 | } 40 | 41 | func (s *Stats) DiskUsed() uint64 { 42 | return s.DiskStats.Used 43 | } 44 | 45 | func (s *Stats) CpuUsage() float64 { 46 | 47 | idle := s.CpuStats.Idle + s.CpuStats.IOWait 48 | nonIdle := s.CpuStats.User + s.CpuStats.Nice + s.CpuStats.System + s.CpuStats.IRQ + s.CpuStats.SoftIRQ + s.CpuStats.Steal 49 | total := idle + nonIdle 50 | 51 | if total == 0 { 52 | return 0.00 53 | } 54 | 55 | return (float64(total) - float64(idle)) / float64(total) 56 | } 57 | 58 | func GetStats() *Stats { 59 | return &Stats{ 60 | MemStats: GetMemoryInfo(), 61 | DiskStats: GetDiskInfo(), 62 | CpuStats: GetCpuStats(), 63 | LoadStats: GetLoadAvg(), 64 | } 65 | } 66 | 67 | // GetMemoryInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#MemInfo 68 | func GetMemoryInfo() *linux.MemInfo { 69 | memstats, err := linux.ReadMemInfo("/proc/meminfo") 70 | if err != nil { 71 | log.Printf("Error reading from /proc/meminfo") 72 | return &linux.MemInfo{} 73 | } 74 | 75 | return memstats 76 | } 77 | 78 | // GetDiskInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#Disk 79 | func GetDiskInfo() *linux.Disk { 80 | diskstats, err := linux.ReadDisk("/") 81 | if err != nil { 82 | log.Printf("Error reading from /") 83 | return &linux.Disk{} 84 | } 85 | 86 | return diskstats 87 | } 88 | 89 | // GetCpuInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#CPUStat 90 | func GetCpuStats() *linux.CPUStat { 91 | stats, err := linux.ReadStat("/proc/stat") 92 | if err != nil { 93 | log.Printf("Error reading from /proc/stat") 94 | return &linux.CPUStat{} 95 | } 96 | 97 | return &stats.CPUStatAll 98 | } 99 | 100 | // GetLoadAvg See https://godoc.org/github.com/c9s/goprocinfo/linux#LoadAvg 101 | func GetLoadAvg() *linux.LoadAvg { 102 | loadavg, err := linux.ReadLoadAvg("/proc/loadavg") 103 | if err != nil { 104 | log.Printf("Error reading from /proc/loadavg") 105 | return &linux.LoadAvg{} 106 | } 107 | 108 | return loadavg 109 | } 110 | -------------------------------------------------------------------------------- /ch10/stats/stats.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/c9s/goprocinfo/linux" 7 | ) 8 | 9 | type Stats struct { 10 | MemStats *linux.MemInfo 11 | DiskStats *linux.Disk 12 | CpuStats *linux.CPUStat 13 | LoadStats *linux.LoadAvg 14 | TaskCount int 15 | } 16 | 17 | func (s *Stats) MemUsedKb() uint64 { 18 | return s.MemStats.MemTotal - s.MemStats.MemAvailable 19 | } 20 | 21 | func (s *Stats) MemUsedPercent() uint64 { 22 | return s.MemStats.MemAvailable / s.MemStats.MemTotal 23 | } 24 | 25 | func (s *Stats) MemAvailableKb() uint64 { 26 | return s.MemStats.MemAvailable 27 | } 28 | 29 | func (s *Stats) MemTotalKb() uint64 { 30 | return s.MemStats.MemTotal 31 | } 32 | 33 | func (s *Stats) DiskTotal() uint64 { 34 | return s.DiskStats.All 35 | } 36 | 37 | func (s *Stats) DiskFree() uint64 { 38 | return s.DiskStats.Free 39 | } 40 | 41 | func (s *Stats) DiskUsed() uint64 { 42 | return s.DiskStats.Used 43 | } 44 | 45 | func (s *Stats) CpuUsage() float64 { 46 | 47 | idle := s.CpuStats.Idle + s.CpuStats.IOWait 48 | nonIdle := s.CpuStats.User + s.CpuStats.Nice + s.CpuStats.System + s.CpuStats.IRQ + s.CpuStats.SoftIRQ + s.CpuStats.Steal 49 | total := idle + nonIdle 50 | 51 | if total == 0 && idle == 0 { 52 | return 0.00 53 | } 54 | 55 | return (float64(total) - float64(idle)) / float64(total) 56 | } 57 | 58 | func GetStats() *Stats { 59 | return &Stats{ 60 | MemStats: GetMemoryInfo(), 61 | DiskStats: GetDiskInfo(), 62 | CpuStats: GetCpuStats(), 63 | LoadStats: GetLoadAvg(), 64 | } 65 | } 66 | 67 | // GetMemoryInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#MemInfo 68 | func GetMemoryInfo() *linux.MemInfo { 69 | memstats, err := linux.ReadMemInfo("/proc/meminfo") 70 | if err != nil { 71 | log.Printf("Error reading from /proc/meminfo") 72 | return &linux.MemInfo{} 73 | } 74 | 75 | return memstats 76 | } 77 | 78 | // GetDiskInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#Disk 79 | func GetDiskInfo() *linux.Disk { 80 | diskstats, err := linux.ReadDisk("/") 81 | if err != nil { 82 | log.Printf("Error reading from /") 83 | return &linux.Disk{} 84 | } 85 | 86 | return diskstats 87 | } 88 | 89 | // GetCpuInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#CPUStat 90 | func GetCpuStats() *linux.CPUStat { 91 | stats, err := linux.ReadStat("/proc/stat") 92 | if err != nil { 93 | log.Printf("Error reading from /proc/stat") 94 | return &linux.CPUStat{} 95 | } 96 | 97 | return &stats.CPUStatAll 98 | } 99 | 100 | // GetLoadAvg See https://godoc.org/github.com/c9s/goprocinfo/linux#LoadAvg 101 | func GetLoadAvg() *linux.LoadAvg { 102 | loadavg, err := linux.ReadLoadAvg("/proc/loadavg") 103 | if err != nil { 104 | log.Printf("Error reading from /proc/loadavg") 105 | return &linux.LoadAvg{} 106 | } 107 | 108 | return loadavg 109 | } 110 | -------------------------------------------------------------------------------- /ch11/stats/stats.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/c9s/goprocinfo/linux" 7 | ) 8 | 9 | type Stats struct { 10 | MemStats *linux.MemInfo 11 | DiskStats *linux.Disk 12 | CpuStats *linux.CPUStat 13 | LoadStats *linux.LoadAvg 14 | TaskCount int 15 | } 16 | 17 | func (s *Stats) MemUsedKb() uint64 { 18 | return s.MemStats.MemTotal - s.MemStats.MemAvailable 19 | } 20 | 21 | func (s *Stats) MemUsedPercent() uint64 { 22 | return s.MemStats.MemAvailable / s.MemStats.MemTotal 23 | } 24 | 25 | func (s *Stats) MemAvailableKb() uint64 { 26 | return s.MemStats.MemAvailable 27 | } 28 | 29 | func (s *Stats) MemTotalKb() uint64 { 30 | return s.MemStats.MemTotal 31 | } 32 | 33 | func (s *Stats) DiskTotal() uint64 { 34 | return s.DiskStats.All 35 | } 36 | 37 | func (s *Stats) DiskFree() uint64 { 38 | return s.DiskStats.Free 39 | } 40 | 41 | func (s *Stats) DiskUsed() uint64 { 42 | return s.DiskStats.Used 43 | } 44 | 45 | func (s *Stats) CpuUsage() float64 { 46 | 47 | idle := s.CpuStats.Idle + s.CpuStats.IOWait 48 | nonIdle := s.CpuStats.User + s.CpuStats.Nice + s.CpuStats.System + s.CpuStats.IRQ + s.CpuStats.SoftIRQ + s.CpuStats.Steal 49 | total := idle + nonIdle 50 | 51 | if total == 0 && idle == 0 { 52 | return 0.00 53 | } 54 | 55 | return (float64(total) - float64(idle)) / float64(total) 56 | } 57 | 58 | func GetStats() *Stats { 59 | return &Stats{ 60 | MemStats: GetMemoryInfo(), 61 | DiskStats: GetDiskInfo(), 62 | CpuStats: GetCpuStats(), 63 | LoadStats: GetLoadAvg(), 64 | } 65 | } 66 | 67 | // GetMemoryInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#MemInfo 68 | func GetMemoryInfo() *linux.MemInfo { 69 | memstats, err := linux.ReadMemInfo("/proc/meminfo") 70 | if err != nil { 71 | log.Printf("Error reading from /proc/meminfo") 72 | return &linux.MemInfo{} 73 | } 74 | 75 | return memstats 76 | } 77 | 78 | // GetDiskInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#Disk 79 | func GetDiskInfo() *linux.Disk { 80 | diskstats, err := linux.ReadDisk("/") 81 | if err != nil { 82 | log.Printf("Error reading from /") 83 | return &linux.Disk{} 84 | } 85 | 86 | return diskstats 87 | } 88 | 89 | // GetCpuInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#CPUStat 90 | func GetCpuStats() *linux.CPUStat { 91 | stats, err := linux.ReadStat("/proc/stat") 92 | if err != nil { 93 | log.Printf("Error reading from /proc/stat") 94 | return &linux.CPUStat{} 95 | } 96 | 97 | return &stats.CPUStatAll 98 | } 99 | 100 | // GetLoadAvg See https://godoc.org/github.com/c9s/goprocinfo/linux#LoadAvg 101 | func GetLoadAvg() *linux.LoadAvg { 102 | loadavg, err := linux.ReadLoadAvg("/proc/loadavg") 103 | if err != nil { 104 | log.Printf("Error reading from /proc/loadavg") 105 | return &linux.LoadAvg{} 106 | } 107 | 108 | return loadavg 109 | } 110 | -------------------------------------------------------------------------------- /ch12/stats/stats.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/c9s/goprocinfo/linux" 7 | ) 8 | 9 | type Stats struct { 10 | MemStats *linux.MemInfo 11 | DiskStats *linux.Disk 12 | CpuStats *linux.CPUStat 13 | LoadStats *linux.LoadAvg 14 | TaskCount int 15 | } 16 | 17 | func (s *Stats) MemUsedKb() uint64 { 18 | return s.MemStats.MemTotal - s.MemStats.MemAvailable 19 | } 20 | 21 | func (s *Stats) MemUsedPercent() uint64 { 22 | return s.MemStats.MemAvailable / s.MemStats.MemTotal 23 | } 24 | 25 | func (s *Stats) MemAvailableKb() uint64 { 26 | return s.MemStats.MemAvailable 27 | } 28 | 29 | func (s *Stats) MemTotalKb() uint64 { 30 | return s.MemStats.MemTotal 31 | } 32 | 33 | func (s *Stats) DiskTotal() uint64 { 34 | return s.DiskStats.All 35 | } 36 | 37 | func (s *Stats) DiskFree() uint64 { 38 | return s.DiskStats.Free 39 | } 40 | 41 | func (s *Stats) DiskUsed() uint64 { 42 | return s.DiskStats.Used 43 | } 44 | 45 | func (s *Stats) CpuUsage() float64 { 46 | 47 | idle := s.CpuStats.Idle + s.CpuStats.IOWait 48 | nonIdle := s.CpuStats.User + s.CpuStats.Nice + s.CpuStats.System + s.CpuStats.IRQ + s.CpuStats.SoftIRQ + s.CpuStats.Steal 49 | total := idle + nonIdle 50 | 51 | if total == 0 && idle == 0 { 52 | return 0.00 53 | } 54 | 55 | return (float64(total) - float64(idle)) / float64(total) 56 | } 57 | 58 | func GetStats() *Stats { 59 | return &Stats{ 60 | MemStats: GetMemoryInfo(), 61 | DiskStats: GetDiskInfo(), 62 | CpuStats: GetCpuStats(), 63 | LoadStats: GetLoadAvg(), 64 | } 65 | } 66 | 67 | // GetMemoryInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#MemInfo 68 | func GetMemoryInfo() *linux.MemInfo { 69 | memstats, err := linux.ReadMemInfo("/proc/meminfo") 70 | if err != nil { 71 | log.Printf("Error reading from /proc/meminfo") 72 | return &linux.MemInfo{} 73 | } 74 | 75 | return memstats 76 | } 77 | 78 | // GetDiskInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#Disk 79 | func GetDiskInfo() *linux.Disk { 80 | diskstats, err := linux.ReadDisk("/") 81 | if err != nil { 82 | log.Printf("Error reading from /") 83 | return &linux.Disk{} 84 | } 85 | 86 | return diskstats 87 | } 88 | 89 | // GetCpuInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#CPUStat 90 | func GetCpuStats() *linux.CPUStat { 91 | stats, err := linux.ReadStat("/proc/stat") 92 | if err != nil { 93 | log.Printf("Error reading from /proc/stat") 94 | return &linux.CPUStat{} 95 | } 96 | 97 | return &stats.CPUStatAll 98 | } 99 | 100 | // GetLoadAvg See https://godoc.org/github.com/c9s/goprocinfo/linux#LoadAvg 101 | func GetLoadAvg() *linux.LoadAvg { 102 | loadavg, err := linux.ReadLoadAvg("/proc/loadavg") 103 | if err != nil { 104 | log.Printf("Error reading from /proc/loadavg") 105 | return &linux.LoadAvg{} 106 | } 107 | 108 | return loadavg 109 | } 110 | -------------------------------------------------------------------------------- /ch7/worker/stats.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/c9s/goprocinfo/linux" 7 | ) 8 | 9 | type Stats struct { 10 | MemStats *linux.MemInfo 11 | DiskStats *linux.Disk 12 | CpuStats *linux.CPUStat 13 | LoadStats *linux.LoadAvg 14 | TaskCount int 15 | } 16 | 17 | func (s *Stats) MemUsedKb() uint64 { 18 | return s.MemStats.MemTotal - s.MemStats.MemAvailable 19 | } 20 | 21 | func (s *Stats) MemUsedPercent() uint64 { 22 | return s.MemStats.MemAvailable / s.MemStats.MemTotal 23 | } 24 | 25 | func (s *Stats) MemAvailableKb() uint64 { 26 | return s.MemStats.MemAvailable 27 | } 28 | 29 | func (s *Stats) MemTotalKb() uint64 { 30 | return s.MemStats.MemTotal 31 | } 32 | 33 | func (s *Stats) DiskTotal() uint64 { 34 | return s.DiskStats.All 35 | } 36 | 37 | func (s *Stats) DiskFree() uint64 { 38 | return s.DiskStats.Free 39 | } 40 | 41 | func (s *Stats) DiskUsed() uint64 { 42 | return s.DiskStats.Used 43 | } 44 | 45 | func (s *Stats) CpuUsage() float64 { 46 | 47 | idle := s.CpuStats.Idle + s.CpuStats.IOWait 48 | nonIdle := s.CpuStats.User + s.CpuStats.Nice + s.CpuStats.System + s.CpuStats.IRQ + s.CpuStats.SoftIRQ + s.CpuStats.Steal 49 | total := idle + nonIdle 50 | 51 | if total == 0 && idle == 0 { 52 | return 0.00 53 | } 54 | 55 | return (float64(total) - float64(idle)) / float64(total) 56 | } 57 | 58 | func GetStats() *Stats { 59 | return &Stats{ 60 | MemStats: GetMemoryInfo(), 61 | DiskStats: GetDiskInfo(), 62 | CpuStats: GetCpuStats(), 63 | LoadStats: GetLoadAvg(), 64 | } 65 | } 66 | 67 | // GetMemoryInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#MemInfo 68 | func GetMemoryInfo() *linux.MemInfo { 69 | memstats, err := linux.ReadMemInfo("/proc/meminfo") 70 | if err != nil { 71 | log.Printf("Error reading from /proc/meminfo") 72 | return &linux.MemInfo{} 73 | } 74 | 75 | return memstats 76 | } 77 | 78 | // GetDiskInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#Disk 79 | func GetDiskInfo() *linux.Disk { 80 | diskstats, err := linux.ReadDisk("/") 81 | if err != nil { 82 | log.Printf("Error reading from /") 83 | return &linux.Disk{} 84 | } 85 | 86 | return diskstats 87 | } 88 | 89 | // GetCpuInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#CPUStat 90 | func GetCpuStats() *linux.CPUStat { 91 | stats, err := linux.ReadStat("/proc/stat") 92 | if err != nil { 93 | log.Printf("Error reading from /proc/stat") 94 | return &linux.CPUStat{} 95 | } 96 | 97 | return &stats.CPUStatAll 98 | } 99 | 100 | // GetLoadAvg See https://godoc.org/github.com/c9s/goprocinfo/linux#LoadAvg 101 | func GetLoadAvg() *linux.LoadAvg { 102 | loadavg, err := linux.ReadLoadAvg("/proc/loadavg") 103 | if err != nil { 104 | log.Printf("Error reading from /proc/loadavg") 105 | return &linux.LoadAvg{} 106 | } 107 | 108 | return loadavg 109 | } 110 | -------------------------------------------------------------------------------- /ch8/worker/stats.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/c9s/goprocinfo/linux" 7 | ) 8 | 9 | type Stats struct { 10 | MemStats *linux.MemInfo 11 | DiskStats *linux.Disk 12 | CpuStats *linux.CPUStat 13 | LoadStats *linux.LoadAvg 14 | TaskCount int 15 | } 16 | 17 | func (s *Stats) MemUsedKb() uint64 { 18 | return s.MemStats.MemTotal - s.MemStats.MemAvailable 19 | } 20 | 21 | func (s *Stats) MemUsedPercent() uint64 { 22 | return s.MemStats.MemAvailable / s.MemStats.MemTotal 23 | } 24 | 25 | func (s *Stats) MemAvailableKb() uint64 { 26 | return s.MemStats.MemAvailable 27 | } 28 | 29 | func (s *Stats) MemTotalKb() uint64 { 30 | return s.MemStats.MemTotal 31 | } 32 | 33 | func (s *Stats) DiskTotal() uint64 { 34 | return s.DiskStats.All 35 | } 36 | 37 | func (s *Stats) DiskFree() uint64 { 38 | return s.DiskStats.Free 39 | } 40 | 41 | func (s *Stats) DiskUsed() uint64 { 42 | return s.DiskStats.Used 43 | } 44 | 45 | func (s *Stats) CpuUsage() float64 { 46 | 47 | idle := s.CpuStats.Idle + s.CpuStats.IOWait 48 | nonIdle := s.CpuStats.User + s.CpuStats.Nice + s.CpuStats.System + s.CpuStats.IRQ + s.CpuStats.SoftIRQ + s.CpuStats.Steal 49 | total := idle + nonIdle 50 | 51 | if total == 0 && idle == 0 { 52 | return 0.00 53 | } 54 | 55 | return (float64(total) - float64(idle)) / float64(total) 56 | } 57 | 58 | func GetStats() *Stats { 59 | return &Stats{ 60 | MemStats: GetMemoryInfo(), 61 | DiskStats: GetDiskInfo(), 62 | CpuStats: GetCpuStats(), 63 | LoadStats: GetLoadAvg(), 64 | } 65 | } 66 | 67 | // GetMemoryInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#MemInfo 68 | func GetMemoryInfo() *linux.MemInfo { 69 | memstats, err := linux.ReadMemInfo("/proc/meminfo") 70 | if err != nil { 71 | log.Printf("Error reading from /proc/meminfo") 72 | return &linux.MemInfo{} 73 | } 74 | 75 | return memstats 76 | } 77 | 78 | // GetDiskInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#Disk 79 | func GetDiskInfo() *linux.Disk { 80 | diskstats, err := linux.ReadDisk("/") 81 | if err != nil { 82 | log.Printf("Error reading from /") 83 | return &linux.Disk{} 84 | } 85 | 86 | return diskstats 87 | } 88 | 89 | // GetCpuInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#CPUStat 90 | func GetCpuStats() *linux.CPUStat { 91 | stats, err := linux.ReadStat("/proc/stat") 92 | if err != nil { 93 | log.Printf("Error reading from /proc/stat") 94 | return &linux.CPUStat{} 95 | } 96 | 97 | return &stats.CPUStatAll 98 | } 99 | 100 | // GetLoadAvg See https://godoc.org/github.com/c9s/goprocinfo/linux#LoadAvg 101 | func GetLoadAvg() *linux.LoadAvg { 102 | loadavg, err := linux.ReadLoadAvg("/proc/loadavg") 103 | if err != nil { 104 | log.Printf("Error reading from /proc/loadavg") 105 | return &linux.LoadAvg{} 106 | } 107 | 108 | return loadavg 109 | } 110 | -------------------------------------------------------------------------------- /ch9/worker/stats.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/c9s/goprocinfo/linux" 7 | ) 8 | 9 | type Stats struct { 10 | MemStats *linux.MemInfo 11 | DiskStats *linux.Disk 12 | CpuStats *linux.CPUStat 13 | LoadStats *linux.LoadAvg 14 | TaskCount int 15 | } 16 | 17 | func (s *Stats) MemUsedKb() uint64 { 18 | return s.MemStats.MemTotal - s.MemStats.MemAvailable 19 | } 20 | 21 | func (s *Stats) MemUsedPercent() uint64 { 22 | return s.MemStats.MemAvailable / s.MemStats.MemTotal 23 | } 24 | 25 | func (s *Stats) MemAvailableKb() uint64 { 26 | return s.MemStats.MemAvailable 27 | } 28 | 29 | func (s *Stats) MemTotalKb() uint64 { 30 | return s.MemStats.MemTotal 31 | } 32 | 33 | func (s *Stats) DiskTotal() uint64 { 34 | return s.DiskStats.All 35 | } 36 | 37 | func (s *Stats) DiskFree() uint64 { 38 | return s.DiskStats.Free 39 | } 40 | 41 | func (s *Stats) DiskUsed() uint64 { 42 | return s.DiskStats.Used 43 | } 44 | 45 | func (s *Stats) CpuUsage() float64 { 46 | 47 | idle := s.CpuStats.Idle + s.CpuStats.IOWait 48 | nonIdle := s.CpuStats.User + s.CpuStats.Nice + s.CpuStats.System + s.CpuStats.IRQ + s.CpuStats.SoftIRQ + s.CpuStats.Steal 49 | total := idle + nonIdle 50 | 51 | if total == 0 && idle == 0 { 52 | return 0.00 53 | } 54 | 55 | return (float64(total) - float64(idle)) / float64(total) 56 | } 57 | 58 | func GetStats() *Stats { 59 | return &Stats{ 60 | MemStats: GetMemoryInfo(), 61 | DiskStats: GetDiskInfo(), 62 | CpuStats: GetCpuStats(), 63 | LoadStats: GetLoadAvg(), 64 | } 65 | } 66 | 67 | // GetMemoryInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#MemInfo 68 | func GetMemoryInfo() *linux.MemInfo { 69 | memstats, err := linux.ReadMemInfo("/proc/meminfo") 70 | if err != nil { 71 | log.Printf("Error reading from /proc/meminfo") 72 | return &linux.MemInfo{} 73 | } 74 | 75 | return memstats 76 | } 77 | 78 | // GetDiskInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#Disk 79 | func GetDiskInfo() *linux.Disk { 80 | diskstats, err := linux.ReadDisk("/") 81 | if err != nil { 82 | log.Printf("Error reading from /") 83 | return &linux.Disk{} 84 | } 85 | 86 | return diskstats 87 | } 88 | 89 | // GetCpuInfo See https://godoc.org/github.com/c9s/goprocinfo/linux#CPUStat 90 | func GetCpuStats() *linux.CPUStat { 91 | stats, err := linux.ReadStat("/proc/stat") 92 | if err != nil { 93 | log.Printf("Error reading from /proc/stat") 94 | return &linux.CPUStat{} 95 | } 96 | 97 | return &stats.CPUStatAll 98 | } 99 | 100 | // GetLoadAvg See https://godoc.org/github.com/c9s/goprocinfo/linux#LoadAvg 101 | func GetLoadAvg() *linux.LoadAvg { 102 | loadavg, err := linux.ReadLoadAvg("/proc/loadavg") 103 | if err != nil { 104 | log.Printf("Error reading from /proc/loadavg") 105 | return &linux.LoadAvg{} 106 | } 107 | 108 | return loadavg 109 | } 110 | -------------------------------------------------------------------------------- /ch11/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "cube/stats" 5 | "cube/task" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 16 | d := json.NewDecoder(r.Body) 17 | 18 | te := task.TaskEvent{} 19 | err := d.Decode(&te) 20 | if err != nil { 21 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 22 | log.Print(msg) 23 | w.WriteHeader(400) 24 | e := ErrResponse{ 25 | HTTPStatusCode: 400, 26 | Message: msg, 27 | } 28 | json.NewEncoder(w).Encode(e) 29 | return 30 | } 31 | 32 | a.Worker.AddTask(te.Task) 33 | log.Printf("[worker] Added task %v\n", te.Task.ID) 34 | w.WriteHeader(201) 35 | json.NewEncoder(w).Encode(te.Task) 36 | } 37 | 38 | func (a *Api) GetStatsHandler(w http.ResponseWriter, r *http.Request) { 39 | w.Header().Set("Content-Type", "application/json") 40 | if a.Worker.Stats != nil { 41 | w.WriteHeader(200) 42 | json.NewEncoder(w).Encode(*a.Worker.Stats) 43 | return 44 | } 45 | 46 | w.WriteHeader(200) 47 | stats := stats.GetStats() 48 | json.NewEncoder(w).Encode(stats) 49 | } 50 | 51 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 52 | w.Header().Set("Content-Type", "application/json") 53 | w.WriteHeader(200) 54 | json.NewEncoder(w).Encode(a.Worker.GetTasks()) 55 | } 56 | 57 | func (a *Api) InspectTaskHandler(w http.ResponseWriter, r *http.Request) { 58 | taskID := chi.URLParam(r, "taskID") 59 | if taskID == "" { 60 | log.Printf("No taskID passed in request.\n") 61 | w.WriteHeader(400) 62 | } 63 | 64 | tID, _ := uuid.Parse(taskID) 65 | t, err := a.Worker.Db.Get(tID.String()) 66 | if err != nil { 67 | log.Printf("No task with ID %v found", tID) 68 | w.WriteHeader(404) 69 | return 70 | } 71 | 72 | resp := a.Worker.InspectTask(t.(task.Task)) 73 | 74 | w.Header().Set("Content-Type", "application/json") 75 | w.WriteHeader(200) 76 | json.NewEncoder(w).Encode(resp.Container) 77 | 78 | } 79 | 80 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 81 | taskID := chi.URLParam(r, "taskID") 82 | if taskID == "" { 83 | log.Printf("No taskID passed in request.\n") 84 | w.WriteHeader(400) 85 | } 86 | 87 | tID, _ := uuid.Parse(taskID) 88 | taskToStop, err := a.Worker.Db.Get(tID.String()) 89 | if err != nil { 90 | log.Printf("No task with ID %v found", tID) 91 | w.WriteHeader(404) 92 | } 93 | 94 | // we need to make a copy so we are not modifying the task in the datastore 95 | taskCopy := *taskToStop.(*task.Task) 96 | taskCopy.State = task.Completed 97 | a.Worker.AddTask(taskCopy) 98 | 99 | log.Printf("Added task %v to stop container %v\n", taskCopy.ID.String(), taskCopy.ContainerID) 100 | w.WriteHeader(204) 101 | } 102 | -------------------------------------------------------------------------------- /ch12/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "cube/stats" 5 | "cube/task" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/google/uuid" 13 | ) 14 | 15 | func (a *Api) StartTaskHandler(w http.ResponseWriter, r *http.Request) { 16 | d := json.NewDecoder(r.Body) 17 | 18 | te := task.TaskEvent{} 19 | err := d.Decode(&te) 20 | if err != nil { 21 | msg := fmt.Sprintf("Error unmarshalling body: %v\n", err) 22 | log.Print(msg) 23 | w.WriteHeader(400) 24 | e := ErrResponse{ 25 | HTTPStatusCode: 400, 26 | Message: msg, 27 | } 28 | json.NewEncoder(w).Encode(e) 29 | return 30 | } 31 | 32 | a.Worker.AddTask(te.Task) 33 | log.Printf("[worker] Added task %v\n", te.Task.ID) 34 | w.WriteHeader(201) 35 | json.NewEncoder(w).Encode(te.Task) 36 | } 37 | 38 | func (a *Api) GetStatsHandler(w http.ResponseWriter, r *http.Request) { 39 | w.Header().Set("Content-Type", "application/json") 40 | if a.Worker.Stats != nil { 41 | w.WriteHeader(200) 42 | json.NewEncoder(w).Encode(*a.Worker.Stats) 43 | return 44 | } 45 | 46 | w.WriteHeader(200) 47 | stats := stats.GetStats() 48 | json.NewEncoder(w).Encode(stats) 49 | } 50 | 51 | func (a *Api) GetTasksHandler(w http.ResponseWriter, r *http.Request) { 52 | w.Header().Set("Content-Type", "application/json") 53 | w.WriteHeader(200) 54 | json.NewEncoder(w).Encode(a.Worker.GetTasks()) 55 | } 56 | 57 | func (a *Api) InspectTaskHandler(w http.ResponseWriter, r *http.Request) { 58 | taskID := chi.URLParam(r, "taskID") 59 | if taskID == "" { 60 | log.Printf("No taskID passed in request.\n") 61 | w.WriteHeader(400) 62 | } 63 | 64 | tID, _ := uuid.Parse(taskID) 65 | t, err := a.Worker.Db.Get(tID.String()) 66 | if err != nil { 67 | log.Printf("No task with ID %v found", tID) 68 | w.WriteHeader(404) 69 | return 70 | } 71 | 72 | resp := a.Worker.InspectTask(t.(task.Task)) 73 | 74 | w.Header().Set("Content-Type", "application/json") 75 | w.WriteHeader(200) 76 | json.NewEncoder(w).Encode(resp.Container) 77 | 78 | } 79 | 80 | func (a *Api) StopTaskHandler(w http.ResponseWriter, r *http.Request) { 81 | taskID := chi.URLParam(r, "taskID") 82 | if taskID == "" { 83 | log.Printf("No taskID passed in request.\n") 84 | w.WriteHeader(400) 85 | } 86 | 87 | tID, _ := uuid.Parse(taskID) 88 | taskToStop, err := a.Worker.Db.Get(tID.String()) 89 | if err != nil { 90 | log.Printf("No task with ID %v found", tID) 91 | w.WriteHeader(404) 92 | } 93 | 94 | // we need to make a copy so we are not modifying the task in the datastore 95 | taskCopy := *taskToStop.(*task.Task) 96 | taskCopy.State = task.Completed 97 | a.Worker.AddTask(taskCopy) 98 | 99 | log.Printf("Added task %v to stop container %v\n", taskCopy.ID.String(), taskCopy.ContainerID) 100 | w.WriteHeader(204) 101 | } 102 | -------------------------------------------------------------------------------- /ch6/worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/golang-collections/collections/queue" 10 | "github.com/google/uuid" 11 | 12 | "cube/task" 13 | ) 14 | 15 | type Worker struct { 16 | Name string 17 | Queue queue.Queue 18 | Db map[uuid.UUID]*task.Task 19 | Stats *Stats 20 | TaskCount int 21 | } 22 | 23 | func (w *Worker) GetTasks() []*task.Task { 24 | tasks := []*task.Task{} 25 | for _, t := range w.Db { 26 | tasks = append(tasks, t) 27 | } 28 | return tasks 29 | } 30 | 31 | func (w *Worker) CollectStats() { 32 | for { 33 | log.Println("Collecting stats") 34 | w.Stats = GetStats() 35 | w.Stats.TaskCount = w.TaskCount 36 | time.Sleep(15 * time.Second) 37 | } 38 | } 39 | 40 | func (w *Worker) AddTask(t task.Task) { 41 | w.Queue.Enqueue(t) 42 | } 43 | 44 | func (w *Worker) RunTask() task.DockerResult { 45 | t := w.Queue.Dequeue() 46 | if t == nil { 47 | log.Println("No tasks in the queue") 48 | return task.DockerResult{Error: nil} 49 | } 50 | 51 | taskQueued := t.(task.Task) 52 | fmt.Printf("Found task in queue: %v:\n", taskQueued) 53 | 54 | taskPersisted := w.Db[taskQueued.ID] 55 | if taskPersisted == nil { 56 | taskPersisted = &taskQueued 57 | w.Db[taskQueued.ID] = &taskQueued 58 | } 59 | 60 | var result task.DockerResult 61 | if task.ValidStateTransition(taskPersisted.State, taskQueued.State) { 62 | switch taskQueued.State { 63 | case task.Scheduled: 64 | result = w.StartTask(taskQueued) 65 | case task.Completed: 66 | result = w.StopTask(taskQueued) 67 | default: 68 | fmt.Printf("This is a mistake. taskPersisted: %v, taskQueued: %v\n", taskPersisted, taskQueued) 69 | result.Error = errors.New("We should not get here") 70 | } 71 | } else { 72 | err := fmt.Errorf("Invalid transition from %v to %v", taskPersisted.State, taskQueued.State) 73 | result.Error = err 74 | return result 75 | } 76 | return result 77 | } 78 | 79 | func (w *Worker) StartTask(t task.Task) task.DockerResult { 80 | config := task.NewConfig(&t) 81 | d := task.NewDocker(config) 82 | result := d.Run() 83 | if result.Error != nil { 84 | log.Printf("Err running task %v: %v\n", t.ID, result.Error) 85 | t.State = task.Failed 86 | w.Db[t.ID] = &t 87 | return result 88 | } 89 | 90 | t.ContainerID = result.ContainerId 91 | t.State = task.Running 92 | w.Db[t.ID] = &t 93 | 94 | return result 95 | } 96 | 97 | func (w *Worker) StopTask(t task.Task) task.DockerResult { 98 | config := task.NewConfig(&t) 99 | d := task.NewDocker(config) 100 | 101 | result := d.Stop(t.ContainerID) 102 | if result.Error != nil { 103 | log.Printf("Error stopping container %v: %v\n", t.ContainerID, result.Error) 104 | } 105 | t.FinishTime = time.Now().UTC() 106 | t.State = task.Completed 107 | w.Db[t.ID] = &t 108 | log.Printf("Stopped and removed container %v for task %v\n", t.ContainerID, t.ID) 109 | 110 | return result 111 | } 112 | -------------------------------------------------------------------------------- /ch7/worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/golang-collections/collections/queue" 10 | "github.com/google/uuid" 11 | 12 | "cube/task" 13 | ) 14 | 15 | type Worker struct { 16 | Name string 17 | Queue queue.Queue 18 | Db map[uuid.UUID]*task.Task 19 | Stats *Stats 20 | TaskCount int 21 | } 22 | 23 | func (w *Worker) GetTasks() []*task.Task { 24 | tasks := []*task.Task{} 25 | for _, t := range w.Db { 26 | tasks = append(tasks, t) 27 | } 28 | return tasks 29 | } 30 | 31 | func (w *Worker) CollectStats() { 32 | for { 33 | log.Println("Collecting stats") 34 | w.Stats = GetStats() 35 | w.TaskCount = w.Stats.TaskCount 36 | time.Sleep(15 * time.Second) 37 | } 38 | } 39 | 40 | func (w *Worker) AddTask(t task.Task) { 41 | w.Queue.Enqueue(t) 42 | } 43 | 44 | func (w *Worker) RunTask() task.DockerResult { 45 | t := w.Queue.Dequeue() 46 | if t == nil { 47 | log.Println("No tasks in the queue") 48 | return task.DockerResult{Error: nil} 49 | } 50 | 51 | taskQueued := t.(task.Task) 52 | fmt.Printf("Found task in queue: %v:\n", taskQueued) 53 | 54 | taskPersisted := w.Db[taskQueued.ID] 55 | if taskPersisted == nil { 56 | taskPersisted = &taskQueued 57 | w.Db[taskQueued.ID] = &taskQueued 58 | } 59 | 60 | var result task.DockerResult 61 | if task.ValidStateTransition(taskPersisted.State, taskQueued.State) { 62 | switch taskQueued.State { 63 | case task.Scheduled: 64 | result = w.StartTask(taskQueued) 65 | case task.Completed: 66 | result = w.StopTask(taskQueued) 67 | default: 68 | fmt.Printf("This is a mistake. taskPersisted: %v, taskQueued: %v\n", taskPersisted, taskQueued) 69 | result.Error = errors.New("We should not get here") 70 | } 71 | } else { 72 | err := fmt.Errorf("Invalid transition from %v to %v", taskPersisted.State, taskQueued.State) 73 | result.Error = err 74 | return result 75 | } 76 | return result 77 | } 78 | 79 | func (w *Worker) StartTask(t task.Task) task.DockerResult { 80 | config := task.NewConfig(&t) 81 | d := task.NewDocker(config) 82 | result := d.Run() 83 | if result.Error != nil { 84 | log.Printf("Err running task %v: %v\n", t.ID, result.Error) 85 | t.State = task.Failed 86 | w.Db[t.ID] = &t 87 | return result 88 | } 89 | 90 | t.ContainerID = result.ContainerId 91 | t.State = task.Running 92 | w.Db[t.ID] = &t 93 | 94 | return result 95 | } 96 | 97 | func (w *Worker) StopTask(t task.Task) task.DockerResult { 98 | config := task.NewConfig(&t) 99 | d := task.NewDocker(config) 100 | 101 | result := d.Stop(t.ContainerID) 102 | if result.Error != nil { 103 | log.Printf("Error stopping container %v: %v\n", t.ContainerID, result.Error) 104 | } 105 | t.FinishTime = time.Now().UTC() 106 | t.State = task.Completed 107 | w.Db[t.ID] = &t 108 | log.Printf("Stopped and removed container %v for task %v\n", t.ContainerID, t.ID) 109 | 110 | return result 111 | } 112 | -------------------------------------------------------------------------------- /ch8/worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/golang-collections/collections/queue" 10 | "github.com/google/uuid" 11 | 12 | "cube/task" 13 | ) 14 | 15 | type Worker struct { 16 | Name string 17 | Queue queue.Queue 18 | Db map[uuid.UUID]*task.Task 19 | Stats *Stats 20 | TaskCount int 21 | } 22 | 23 | func (w *Worker) GetTasks() []*task.Task { 24 | tasks := []*task.Task{} 25 | for _, t := range w.Db { 26 | tasks = append(tasks, t) 27 | } 28 | return tasks 29 | } 30 | 31 | func (w *Worker) CollectStats() { 32 | for { 33 | log.Println("Collecting stats") 34 | w.Stats = GetStats() 35 | w.TaskCount = w.Stats.TaskCount 36 | time.Sleep(15 * time.Second) 37 | } 38 | } 39 | 40 | func (w *Worker) AddTask(t task.Task) { 41 | w.Queue.Enqueue(t) 42 | } 43 | 44 | func (w *Worker) RunTasks() { 45 | for { 46 | if w.Queue.Len() != 0 { 47 | result := w.runTask() 48 | if result.Error != nil { 49 | log.Printf("Error running task: %v\n", result.Error) 50 | } 51 | } else { 52 | log.Printf("No tasks to process currently.\n") 53 | } 54 | log.Println("Sleeping for 10 seconds.") 55 | time.Sleep(10 * time.Second) 56 | } 57 | 58 | } 59 | 60 | func (w *Worker) runTask() task.DockerResult { 61 | t := w.Queue.Dequeue() 62 | if t == nil { 63 | log.Println("No tasks in the queue") 64 | return task.DockerResult{Error: nil} 65 | } 66 | 67 | taskQueued := t.(task.Task) 68 | fmt.Printf("Found task in queue: %v:\n", taskQueued) 69 | 70 | taskPersisted := w.Db[taskQueued.ID] 71 | if taskPersisted == nil { 72 | taskPersisted = &taskQueued 73 | w.Db[taskQueued.ID] = &taskQueued 74 | } 75 | 76 | var result task.DockerResult 77 | if task.ValidStateTransition(taskPersisted.State, taskQueued.State) { 78 | switch taskQueued.State { 79 | case task.Scheduled: 80 | result = w.StartTask(taskQueued) 81 | case task.Completed: 82 | result = w.StopTask(taskQueued) 83 | default: 84 | fmt.Printf("This is a mistake. taskPersisted: %v, taskQueued: %v\n", taskPersisted, taskQueued) 85 | result.Error = errors.New("We should not get here") 86 | } 87 | } else { 88 | err := fmt.Errorf("Invalid transition from %v to %v", taskPersisted.State, taskQueued.State) 89 | result.Error = err 90 | return result 91 | } 92 | return result 93 | } 94 | 95 | func (w *Worker) StartTask(t task.Task) task.DockerResult { 96 | config := task.NewConfig(&t) 97 | d := task.NewDocker(config) 98 | result := d.Run() 99 | if result.Error != nil { 100 | log.Printf("Err running task %v: %v\n", t.ID, result.Error) 101 | t.State = task.Failed 102 | w.Db[t.ID] = &t 103 | return result 104 | } 105 | 106 | t.ContainerID = result.ContainerId 107 | t.State = task.Running 108 | w.Db[t.ID] = &t 109 | 110 | return result 111 | } 112 | 113 | func (w *Worker) StopTask(t task.Task) task.DockerResult { 114 | config := task.NewConfig(&t) 115 | d := task.NewDocker(config) 116 | 117 | result := d.Stop(t.ContainerID) 118 | if result.Error != nil { 119 | log.Printf("Error stopping container %v: %v\n", t.ContainerID, result.Error) 120 | } 121 | t.FinishTime = time.Now().UTC() 122 | t.State = task.Completed 123 | w.Db[t.ID] = &t 124 | log.Printf("Stopped and removed container %v for task %v\n", t.ContainerID, t.ID) 125 | 126 | return result 127 | } 128 | -------------------------------------------------------------------------------- /ch7/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "bytes" 5 | "cube/task" 6 | "cube/worker" 7 | "encoding/json" 8 | "fmt" 9 | "log" 10 | "net/http" 11 | 12 | "github.com/golang-collections/collections/queue" 13 | "github.com/google/uuid" 14 | ) 15 | 16 | type Manager struct { 17 | Pending queue.Queue 18 | TaskDb map[uuid.UUID]*task.Task 19 | EventDb map[uuid.UUID]*task.TaskEvent 20 | Workers []string 21 | WorkerTaskMap map[string][]uuid.UUID 22 | TaskWorkerMap map[uuid.UUID]string 23 | LastWorker int 24 | } 25 | 26 | func New(workers []string) *Manager { 27 | taskDb := make(map[uuid.UUID]*task.Task) 28 | eventDb := make(map[uuid.UUID]*task.TaskEvent) 29 | workerTaskMap := make(map[string][]uuid.UUID) 30 | taskWorkerMap := make(map[uuid.UUID]string) 31 | for worker := range workers { 32 | workerTaskMap[workers[worker]] = []uuid.UUID{} 33 | } 34 | 35 | return &Manager{ 36 | Pending: *queue.New(), 37 | Workers: workers, 38 | TaskDb: taskDb, 39 | EventDb: eventDb, 40 | WorkerTaskMap: workerTaskMap, 41 | TaskWorkerMap: taskWorkerMap, 42 | } 43 | } 44 | 45 | func (m *Manager) SelectWorker() string { 46 | var newWorker int 47 | if m.LastWorker+1 < len(m.Workers) { 48 | newWorker = m.LastWorker + 1 49 | m.LastWorker++ 50 | } else { 51 | newWorker = 0 52 | m.LastWorker = 0 53 | } 54 | 55 | return m.Workers[newWorker] 56 | } 57 | 58 | func (m *Manager) UpdateTasks() { 59 | for _, worker := range m.Workers { 60 | log.Printf("Checking worker %v for task updates\n", worker) 61 | url := fmt.Sprintf("http://%s/tasks", worker) 62 | resp, err := http.Get(url) 63 | if err != nil { 64 | log.Printf("Error connecting to %v: %v\n", worker, err) 65 | } 66 | 67 | if resp.StatusCode != http.StatusOK { 68 | log.Printf("Error sending request: %v\n", err) 69 | } 70 | 71 | d := json.NewDecoder(resp.Body) 72 | var tasks []*task.Task 73 | err = d.Decode(&tasks) 74 | if err != nil { 75 | log.Printf("Error unmarshalling tasks: %s\n", err.Error()) 76 | } 77 | 78 | for _, t := range tasks { 79 | log.Printf("Attempting to update task %v\n", t.ID) 80 | 81 | _, ok := m.TaskDb[t.ID] 82 | if !ok { 83 | log.Printf("Task with ID %s not found\n", t.ID) 84 | return 85 | } 86 | if m.TaskDb[t.ID].State != t.State { 87 | m.TaskDb[t.ID].State = t.State 88 | } 89 | 90 | m.TaskDb[t.ID].StartTime = t.StartTime 91 | m.TaskDb[t.ID].FinishTime = t.FinishTime 92 | m.TaskDb[t.ID].ContainerID = t.ContainerID 93 | } 94 | 95 | } 96 | } 97 | 98 | func (m *Manager) SendWork() { 99 | if m.Pending.Len() > 0 { 100 | w := m.SelectWorker() 101 | 102 | e := m.Pending.Dequeue() 103 | te := e.(task.TaskEvent) 104 | t := te.Task 105 | log.Printf("Pulled %v off pending queue\n", t) 106 | 107 | m.EventDb[te.ID] = &te 108 | m.WorkerTaskMap[w] = append(m.WorkerTaskMap[w], te.Task.ID) 109 | m.TaskWorkerMap[t.ID] = w 110 | 111 | t.State = task.Scheduled 112 | m.TaskDb[t.ID] = &t 113 | 114 | data, err := json.Marshal(te) 115 | if err != nil { 116 | log.Printf("Unable to marshal task object: %v.\n", t) 117 | } 118 | 119 | url := fmt.Sprintf("http://%s/tasks", w) 120 | resp, err := http.Post(url, "application/json", bytes.NewBuffer(data)) 121 | if err != nil { 122 | log.Printf("Error connecting to %v: %v\n", w, err) 123 | m.Pending.Enqueue(te) 124 | return 125 | } 126 | 127 | d := json.NewDecoder(resp.Body) 128 | if resp.StatusCode != http.StatusCreated { 129 | e := worker.ErrResponse{} 130 | err := d.Decode(&e) 131 | if err != nil { 132 | fmt.Printf("Error decoding response: %s\n", err.Error()) 133 | return 134 | } 135 | log.Printf("Response error (%d): %s\n", e.HTTPStatusCode, e.Message) 136 | return 137 | } 138 | 139 | t = task.Task{} 140 | err = d.Decode(&t) 141 | if err != nil { 142 | fmt.Printf("Error decoding response: %s\n", err.Error()) 143 | return 144 | } 145 | log.Printf("%#v\n", t) 146 | } else { 147 | log.Println("No work in the queue") 148 | } 149 | } 150 | 151 | func (m *Manager) AddTask(te task.TaskEvent) { 152 | m.Pending.Enqueue(te) 153 | } 154 | -------------------------------------------------------------------------------- /ch8/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "bytes" 5 | "cube/task" 6 | "cube/worker" 7 | "encoding/json" 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/golang-collections/collections/queue" 14 | "github.com/google/uuid" 15 | ) 16 | 17 | type Manager struct { 18 | Pending queue.Queue 19 | TaskDb map[uuid.UUID]*task.Task 20 | EventDb map[uuid.UUID]*task.TaskEvent 21 | Workers []string 22 | WorkerTaskMap map[string][]uuid.UUID 23 | TaskWorkerMap map[uuid.UUID]string 24 | LastWorker int 25 | } 26 | 27 | func New(workers []string) *Manager { 28 | taskDb := make(map[uuid.UUID]*task.Task) 29 | eventDb := make(map[uuid.UUID]*task.TaskEvent) 30 | workerTaskMap := make(map[string][]uuid.UUID) 31 | taskWorkerMap := make(map[uuid.UUID]string) 32 | for worker := range workers { 33 | workerTaskMap[workers[worker]] = []uuid.UUID{} 34 | } 35 | 36 | return &Manager{ 37 | Pending: *queue.New(), 38 | Workers: workers, 39 | TaskDb: taskDb, 40 | EventDb: eventDb, 41 | WorkerTaskMap: workerTaskMap, 42 | TaskWorkerMap: taskWorkerMap, 43 | } 44 | } 45 | 46 | func (m *Manager) SelectWorker() string { 47 | var newWorker int 48 | if m.LastWorker+1 < len(m.Workers) { 49 | newWorker = m.LastWorker + 1 50 | m.LastWorker++ 51 | } else { 52 | newWorker = 0 53 | m.LastWorker = 0 54 | } 55 | 56 | return m.Workers[newWorker] 57 | } 58 | 59 | func (m *Manager) UpdateTasks() { 60 | for { 61 | log.Println("Checking for task updates from workers") 62 | m.updateTasks() 63 | log.Println("Task updates completed") 64 | log.Println("Sleeping for 15 seconds") 65 | time.Sleep(15 * time.Second) 66 | } 67 | } 68 | 69 | func (m *Manager) updateTasks() { 70 | for _, worker := range m.Workers { 71 | log.Printf("Checking worker %v for task updates", worker) 72 | url := fmt.Sprintf("http://%s/tasks", worker) 73 | resp, err := http.Get(url) 74 | if err != nil { 75 | log.Printf("Error connecting to %v: %v", worker, err) 76 | } 77 | 78 | if resp.StatusCode != http.StatusOK { 79 | log.Printf("Error sending request: %v", err) 80 | } 81 | 82 | d := json.NewDecoder(resp.Body) 83 | var tasks []*task.Task 84 | err = d.Decode(&tasks) 85 | if err != nil { 86 | log.Printf("Error unmarshalling tasks: %s", err.Error()) 87 | } 88 | 89 | for _, t := range tasks { 90 | log.Printf("Attempting to update task %v", t.ID) 91 | 92 | _, ok := m.TaskDb[t.ID] 93 | if !ok { 94 | log.Printf("Task with ID %s not found\n", t.ID) 95 | return 96 | } 97 | if m.TaskDb[t.ID].State != t.State { 98 | m.TaskDb[t.ID].State = t.State 99 | } 100 | 101 | m.TaskDb[t.ID].StartTime = t.StartTime 102 | m.TaskDb[t.ID].FinishTime = t.FinishTime 103 | m.TaskDb[t.ID].ContainerID = t.ContainerID 104 | } 105 | 106 | } 107 | } 108 | 109 | func (m *Manager) ProcessTasks() { 110 | for { 111 | log.Println("Processing any tasks in the queue") 112 | m.SendWork() 113 | log.Println("Sleeping for 10 seconds") 114 | time.Sleep(10 * time.Second) 115 | } 116 | } 117 | 118 | func (m *Manager) SendWork() { 119 | if m.Pending.Len() > 0 { 120 | w := m.SelectWorker() 121 | 122 | e := m.Pending.Dequeue() 123 | te := e.(task.TaskEvent) 124 | t := te.Task 125 | log.Printf("Pulled %v off pending queue", t) 126 | 127 | m.EventDb[te.ID] = &te 128 | m.WorkerTaskMap[w] = append(m.WorkerTaskMap[w], te.Task.ID) 129 | m.TaskWorkerMap[t.ID] = w 130 | 131 | t.State = task.Scheduled 132 | m.TaskDb[t.ID] = &t 133 | 134 | data, err := json.Marshal(te) 135 | if err != nil { 136 | log.Printf("Unable to marshal task object: %v.", t) 137 | } 138 | 139 | url := fmt.Sprintf("http://%s/tasks", w) 140 | resp, err := http.Post(url, "application/json", bytes.NewBuffer(data)) 141 | if err != nil { 142 | log.Printf("Error connecting to %v: %v", w, err) 143 | m.Pending.Enqueue(t) 144 | return 145 | } 146 | 147 | d := json.NewDecoder(resp.Body) 148 | if resp.StatusCode != http.StatusCreated { 149 | e := worker.ErrResponse{} 150 | err := d.Decode(&e) 151 | if err != nil { 152 | fmt.Printf("Error decoding response: %s\n", err.Error()) 153 | return 154 | } 155 | log.Printf("Response error (%d): %s", e.HTTPStatusCode, e.Message) 156 | return 157 | } 158 | 159 | t = task.Task{} 160 | err = d.Decode(&t) 161 | if err != nil { 162 | fmt.Printf("Error decoding response: %s\n", err.Error()) 163 | return 164 | } 165 | log.Printf("%#v\n", t) 166 | } else { 167 | log.Println("No work in the queue") 168 | } 169 | } 170 | 171 | func (m *Manager) GetTasks() []*task.Task { 172 | tasks := []*task.Task{} 173 | for _, t := range m.TaskDb { 174 | tasks = append(tasks, t) 175 | } 176 | return tasks 177 | } 178 | 179 | func (m *Manager) AddTask(te task.TaskEvent) { 180 | m.Pending.Enqueue(te) 181 | } 182 | -------------------------------------------------------------------------------- /ch3/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "context" 10 | 11 | "github.com/docker/docker/api/types" 12 | "github.com/docker/docker/api/types/container" 13 | "github.com/docker/docker/client" 14 | "github.com/docker/go-connections/nat" 15 | "github.com/google/uuid" 16 | "github.com/moby/moby/pkg/stdcopy" 17 | ) 18 | 19 | type Task struct { 20 | ID uuid.UUID 21 | ContainerID string 22 | Name string 23 | State State 24 | Image string 25 | Cpu float64 26 | Memory int64 27 | Disk int64 28 | ExposedPorts nat.PortSet 29 | PortBindings map[string]string 30 | RestartPolicy string 31 | StartTime time.Time 32 | FinishTime time.Time 33 | } 34 | 35 | type TaskEvent struct { 36 | ID uuid.UUID 37 | State State 38 | Timestamp time.Time 39 | Task Task 40 | } 41 | 42 | // Config struct to hold Docker container config 43 | type Config struct { 44 | // Name of the task, also used as the container name 45 | Name string 46 | // AttachStdin boolean which determines if stdin should be attached 47 | AttachStdin bool 48 | // AttachStdout boolean which determines if stdout should be attached 49 | AttachStdout bool 50 | // AttachStderr boolean which determines if stderr should be attached 51 | AttachStderr bool 52 | // ExposedPorts list of ports exposed 53 | ExposedPorts nat.PortSet 54 | // Cmd to be run inside container (optional) 55 | Cmd []string 56 | // Image used to run the container 57 | Image string 58 | // Cpu 59 | Cpu float64 60 | // Memory in MiB 61 | Memory int64 62 | // Disk in GiB 63 | Disk int64 64 | // Env variables 65 | Env []string 66 | // RestartPolicy for the container ["", "always", "unless-stopped", "on-failure"] 67 | RestartPolicy string 68 | } 69 | 70 | func NewConfig(t *Task) *Config { 71 | return &Config{ 72 | Name: t.Name, 73 | ExposedPorts: t.ExposedPorts, 74 | Image: t.Image, 75 | Cpu: t.Cpu, 76 | Memory: t.Memory, 77 | Disk: t.Disk, 78 | RestartPolicy: t.RestartPolicy, 79 | } 80 | } 81 | 82 | type Docker struct { 83 | Client *client.Client 84 | Config Config 85 | } 86 | 87 | func NewDocker(c *Config) *Docker { 88 | dc, _ := client.NewClientWithOpts(client.FromEnv) 89 | return &Docker{ 90 | Client: dc, 91 | Config: *c, 92 | } 93 | } 94 | 95 | type DockerResult struct { 96 | Error error 97 | Action string 98 | ContainerId string 99 | Result string 100 | } 101 | 102 | func (d *Docker) Run() DockerResult { 103 | ctx := context.Background() 104 | reader, err := d.Client.ImagePull(ctx, d.Config.Image, types.ImagePullOptions{}) 105 | if err != nil { 106 | log.Printf("Error pulling image %s: %v\n", d.Config.Image, err) 107 | return DockerResult{Error: err} 108 | } 109 | io.Copy(os.Stdout, reader) 110 | 111 | rp := container.RestartPolicy{ 112 | Name: d.Config.RestartPolicy, 113 | } 114 | 115 | r := container.Resources{ 116 | Memory: d.Config.Memory, 117 | } 118 | 119 | cc := container.Config{ 120 | Image: d.Config.Image, 121 | Tty: false, 122 | Env: d.Config.Env, 123 | ExposedPorts: d.Config.ExposedPorts, 124 | } 125 | 126 | hc := container.HostConfig{ 127 | RestartPolicy: rp, 128 | Resources: r, 129 | PublishAllPorts: true, 130 | } 131 | 132 | resp, err := d.Client.ContainerCreate(ctx, &cc, &hc, nil, nil, d.Config.Name) 133 | if err != nil { 134 | log.Printf("Error creating container using image %s: %v\n", d.Config.Image, err) 135 | return DockerResult{Error: err} 136 | } 137 | 138 | if err = d.Client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { 139 | log.Printf("Error starting container %s: %v\n", resp.ID, err) 140 | return DockerResult{Error: err} 141 | } 142 | 143 | out, err := d.Client.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) 144 | if err != nil { 145 | log.Printf("Error getting logs for container %s: %v\n", resp.ID, err) 146 | return DockerResult{Error: err} 147 | } 148 | 149 | stdcopy.StdCopy(os.Stdout, os.Stderr, out) 150 | 151 | return DockerResult{ContainerId: resp.ID, Action: "start", Result: "success"} 152 | } 153 | 154 | func (d *Docker) Stop(id string) DockerResult { 155 | log.Printf("Attempting to stop container %v", id) 156 | ctx := context.Background() 157 | err := d.Client.ContainerStop(ctx, id, nil) 158 | if err != nil { 159 | log.Printf("Error stopping container %s: %v\n", id, err) 160 | return DockerResult{Error: err} 161 | } 162 | 163 | err = d.Client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{ 164 | RemoveVolumes: true, 165 | RemoveLinks: false, 166 | Force: false, 167 | }) 168 | if err != nil { 169 | log.Printf("Error removing container %s: %v\n", id, err) 170 | return DockerResult{Error: err} 171 | } 172 | 173 | return DockerResult{Action: "stop", Result: "success", Error: nil} 174 | } 175 | -------------------------------------------------------------------------------- /ch10/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "math" 7 | "os" 8 | "time" 9 | 10 | "context" 11 | 12 | "github.com/docker/docker/api/types" 13 | "github.com/docker/docker/api/types/container" 14 | "github.com/docker/docker/client" 15 | "github.com/docker/go-connections/nat" 16 | "github.com/google/uuid" 17 | "github.com/moby/moby/pkg/stdcopy" 18 | ) 19 | 20 | type Task struct { 21 | ID uuid.UUID 22 | ContainerID string 23 | Name string 24 | State State 25 | Image string 26 | Cpu float64 27 | Memory int64 28 | Disk int64 29 | ExposedPorts nat.PortSet 30 | PortBindings map[string]string 31 | RestartPolicy string 32 | StartTime time.Time 33 | FinishTime time.Time 34 | } 35 | 36 | type TaskEvent struct { 37 | ID uuid.UUID 38 | State State 39 | Timestamp time.Time 40 | Task Task 41 | } 42 | 43 | // Config struct to hold Docker container config 44 | type Config struct { 45 | // Name of the task, also used as the container name 46 | Name string 47 | // AttachStdin boolean which determines if stdin should be attached 48 | AttachStdin bool 49 | // AttachStdout boolean which determines if stdout should be attached 50 | AttachStdout bool 51 | // AttachStderr boolean which determines if stderr should be attached 52 | AttachStderr bool 53 | // ExposedPorts list of ports exposed 54 | ExposedPorts nat.PortSet 55 | // Cmd to be run inside container (optional) 56 | Cmd []string 57 | // Image used to run the container 58 | Image string 59 | // Cpu 60 | Cpu float64 61 | // Memory in MiB 62 | Memory int64 63 | // Disk in GiB 64 | Disk int64 65 | // Env variables 66 | Env []string 67 | // RestartPolicy for the container ["", "always", "unless-stopped", "on-failure"] 68 | RestartPolicy string 69 | } 70 | 71 | func NewConfig(t *Task) *Config { 72 | return &Config{ 73 | Name: t.Name, 74 | ExposedPorts: t.ExposedPorts, 75 | Image: t.Image, 76 | Cpu: t.Cpu, 77 | Memory: t.Memory, 78 | Disk: t.Disk, 79 | RestartPolicy: t.RestartPolicy, 80 | } 81 | } 82 | 83 | type Docker struct { 84 | Client *client.Client 85 | Config Config 86 | } 87 | 88 | func NewDocker(c *Config) *Docker { 89 | dc, _ := client.NewClientWithOpts(client.FromEnv) 90 | return &Docker{ 91 | Client: dc, 92 | Config: *c, 93 | } 94 | } 95 | 96 | type DockerResult struct { 97 | Error error 98 | Action string 99 | ContainerId string 100 | Result string 101 | } 102 | 103 | func (d *Docker) Run() DockerResult { 104 | ctx := context.Background() 105 | reader, err := d.Client.ImagePull(ctx, d.Config.Image, types.ImagePullOptions{}) 106 | if err != nil { 107 | log.Printf("Error pulling image %s: %v\n", d.Config.Image, err) 108 | return DockerResult{Error: err} 109 | } 110 | io.Copy(os.Stdout, reader) 111 | 112 | rp := container.RestartPolicy{ 113 | Name: d.Config.RestartPolicy, 114 | } 115 | 116 | r := container.Resources{ 117 | Memory: d.Config.Memory, 118 | NanoCPUs: int64(d.Config.Cpu * math.Pow(10, 9)), 119 | } 120 | 121 | hc := container.HostConfig{ 122 | RestartPolicy: rp, 123 | Resources: r, 124 | PublishAllPorts: true, 125 | } 126 | 127 | resp, err := d.Client.ContainerCreate(ctx, &container.Config{ 128 | Image: d.Config.Image, 129 | Tty: false, 130 | Env: d.Config.Env, 131 | ExposedPorts: d.Config.ExposedPorts, 132 | }, &hc, nil, nil, d.Config.Name) 133 | if err != nil { 134 | log.Printf("Error creating container using image %s: %v\n", d.Config.Image, err) 135 | return DockerResult{Error: err} 136 | } 137 | 138 | if err := d.Client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { 139 | log.Printf("Error starting container %s: %v\n", resp.ID, err) 140 | return DockerResult{Error: err} 141 | } 142 | 143 | out, err := d.Client.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) 144 | if err != nil { 145 | log.Printf("Error getting logs for container %s: %v\n", resp.ID, err) 146 | return DockerResult{Error: err} 147 | } 148 | 149 | stdcopy.StdCopy(os.Stdout, os.Stderr, out) 150 | 151 | return DockerResult{ContainerId: resp.ID, Action: "start", Result: "success"} 152 | } 153 | 154 | func (d *Docker) Stop(id string) DockerResult { 155 | log.Printf("Attempting to stop container %v", id) 156 | ctx := context.Background() 157 | err := d.Client.ContainerStop(ctx, id, nil) 158 | if err != nil { 159 | log.Printf("Error stopping container %s: %v\n", id, err) 160 | return DockerResult{Error: err} 161 | } 162 | 163 | err = d.Client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{ 164 | RemoveVolumes: true, 165 | RemoveLinks: false, 166 | Force: false, 167 | }) 168 | if err != nil { 169 | log.Printf("Error removing container %s: %v\n", id, err) 170 | return DockerResult{Error: err} 171 | } 172 | 173 | return DockerResult{Action: "stop", Result: "success", Error: nil} 174 | } 175 | -------------------------------------------------------------------------------- /ch11/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "math" 7 | "os" 8 | "time" 9 | 10 | "context" 11 | 12 | "github.com/docker/docker/api/types" 13 | "github.com/docker/docker/api/types/container" 14 | "github.com/docker/docker/client" 15 | "github.com/docker/go-connections/nat" 16 | "github.com/google/uuid" 17 | "github.com/moby/moby/pkg/stdcopy" 18 | ) 19 | 20 | type Task struct { 21 | ID uuid.UUID 22 | ContainerID string 23 | Name string 24 | State State 25 | Image string 26 | Cpu float64 27 | Memory int64 28 | Disk int64 29 | ExposedPorts nat.PortSet 30 | PortBindings map[string]string 31 | RestartPolicy string 32 | StartTime time.Time 33 | FinishTime time.Time 34 | } 35 | 36 | type TaskEvent struct { 37 | ID uuid.UUID 38 | State State 39 | Timestamp time.Time 40 | Task Task 41 | } 42 | 43 | // Config struct to hold Docker container config 44 | type Config struct { 45 | // Name of the task, also used as the container name 46 | Name string 47 | // AttachStdin boolean which determines if stdin should be attached 48 | AttachStdin bool 49 | // AttachStdout boolean which determines if stdout should be attached 50 | AttachStdout bool 51 | // AttachStderr boolean which determines if stderr should be attached 52 | AttachStderr bool 53 | // ExposedPorts list of ports exposed 54 | ExposedPorts nat.PortSet 55 | // Cmd to be run inside container (optional) 56 | Cmd []string 57 | // Image used to run the container 58 | Image string 59 | // Cpu 60 | Cpu float64 61 | // Memory in MiB 62 | Memory int64 63 | // Disk in GiB 64 | Disk int64 65 | // Env variables 66 | Env []string 67 | // RestartPolicy for the container ["", "always", "unless-stopped", "on-failure"] 68 | RestartPolicy string 69 | } 70 | 71 | func NewConfig(t *Task) *Config { 72 | return &Config{ 73 | Name: t.Name, 74 | ExposedPorts: t.ExposedPorts, 75 | Image: t.Image, 76 | Cpu: t.Cpu, 77 | Memory: t.Memory, 78 | Disk: t.Disk, 79 | RestartPolicy: t.RestartPolicy, 80 | } 81 | } 82 | 83 | type Docker struct { 84 | Client *client.Client 85 | Config Config 86 | } 87 | 88 | func NewDocker(c *Config) *Docker { 89 | dc, _ := client.NewClientWithOpts(client.FromEnv) 90 | return &Docker{ 91 | Client: dc, 92 | Config: *c, 93 | } 94 | } 95 | 96 | type DockerResult struct { 97 | Error error 98 | Action string 99 | ContainerId string 100 | Result string 101 | } 102 | 103 | func (d *Docker) Run() DockerResult { 104 | ctx := context.Background() 105 | reader, err := d.Client.ImagePull(ctx, d.Config.Image, types.ImagePullOptions{}) 106 | if err != nil { 107 | log.Printf("Error pulling image %s: %v\n", d.Config.Image, err) 108 | return DockerResult{Error: err} 109 | } 110 | io.Copy(os.Stdout, reader) 111 | 112 | rp := container.RestartPolicy{ 113 | Name: d.Config.RestartPolicy, 114 | } 115 | 116 | r := container.Resources{ 117 | Memory: d.Config.Memory, 118 | NanoCPUs: int64(d.Config.Cpu * math.Pow(10, 9)), 119 | } 120 | 121 | hc := container.HostConfig{ 122 | RestartPolicy: rp, 123 | Resources: r, 124 | PublishAllPorts: true, 125 | } 126 | 127 | resp, err := d.Client.ContainerCreate(ctx, &container.Config{ 128 | Image: d.Config.Image, 129 | Tty: false, 130 | Env: d.Config.Env, 131 | ExposedPorts: d.Config.ExposedPorts, 132 | }, &hc, nil, nil, d.Config.Name) 133 | if err != nil { 134 | log.Printf("Error creating container using image %s: %v\n", d.Config.Image, err) 135 | return DockerResult{Error: err} 136 | } 137 | 138 | if err := d.Client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { 139 | log.Printf("Error starting container %s: %v\n", resp.ID, err) 140 | return DockerResult{Error: err} 141 | } 142 | 143 | out, err := d.Client.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) 144 | if err != nil { 145 | log.Printf("Error getting logs for container %s: %v\n", resp.ID, err) 146 | return DockerResult{Error: err} 147 | } 148 | 149 | stdcopy.StdCopy(os.Stdout, os.Stderr, out) 150 | 151 | return DockerResult{ContainerId: resp.ID, Action: "start", Result: "success"} 152 | } 153 | 154 | func (d *Docker) Stop(id string) DockerResult { 155 | log.Printf("Attempting to stop container %v", id) 156 | ctx := context.Background() 157 | err := d.Client.ContainerStop(ctx, id, nil) 158 | if err != nil { 159 | log.Printf("Error stopping container %s: %v\n", id, err) 160 | return DockerResult{Error: err} 161 | } 162 | 163 | err = d.Client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{ 164 | RemoveVolumes: true, 165 | RemoveLinks: false, 166 | Force: false, 167 | }) 168 | if err != nil { 169 | log.Printf("Error removing container %s: %v\n", id, err) 170 | return DockerResult{Error: err} 171 | } 172 | 173 | return DockerResult{Action: "stop", Result: "success", Error: nil} 174 | } 175 | -------------------------------------------------------------------------------- /ch12/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "math" 7 | "os" 8 | "time" 9 | 10 | "context" 11 | 12 | "github.com/docker/docker/api/types" 13 | "github.com/docker/docker/api/types/container" 14 | "github.com/docker/docker/client" 15 | "github.com/docker/go-connections/nat" 16 | "github.com/google/uuid" 17 | "github.com/moby/moby/pkg/stdcopy" 18 | ) 19 | 20 | type Task struct { 21 | ID uuid.UUID 22 | ContainerID string 23 | Name string 24 | State State 25 | Image string 26 | Cpu float64 27 | Memory int64 28 | Disk int64 29 | ExposedPorts nat.PortSet 30 | PortBindings map[string]string 31 | RestartPolicy string 32 | StartTime time.Time 33 | FinishTime time.Time 34 | } 35 | 36 | type TaskEvent struct { 37 | ID uuid.UUID 38 | State State 39 | Timestamp time.Time 40 | Task Task 41 | } 42 | 43 | // Config struct to hold Docker container config 44 | type Config struct { 45 | // Name of the task, also used as the container name 46 | Name string 47 | // AttachStdin boolean which determines if stdin should be attached 48 | AttachStdin bool 49 | // AttachStdout boolean which determines if stdout should be attached 50 | AttachStdout bool 51 | // AttachStderr boolean which determines if stderr should be attached 52 | AttachStderr bool 53 | // ExposedPorts list of ports exposed 54 | ExposedPorts nat.PortSet 55 | // Cmd to be run inside container (optional) 56 | Cmd []string 57 | // Image used to run the container 58 | Image string 59 | // Cpu 60 | Cpu float64 61 | // Memory in MiB 62 | Memory int64 63 | // Disk in GiB 64 | Disk int64 65 | // Env variables 66 | Env []string 67 | // RestartPolicy for the container ["", "always", "unless-stopped", "on-failure"] 68 | RestartPolicy string 69 | } 70 | 71 | func NewConfig(t *Task) *Config { 72 | return &Config{ 73 | Name: t.Name, 74 | ExposedPorts: t.ExposedPorts, 75 | Image: t.Image, 76 | Cpu: t.Cpu, 77 | Memory: t.Memory, 78 | Disk: t.Disk, 79 | RestartPolicy: t.RestartPolicy, 80 | } 81 | } 82 | 83 | type Docker struct { 84 | Client *client.Client 85 | Config Config 86 | } 87 | 88 | func NewDocker(c *Config) *Docker { 89 | dc, _ := client.NewClientWithOpts(client.FromEnv) 90 | return &Docker{ 91 | Client: dc, 92 | Config: *c, 93 | } 94 | } 95 | 96 | type DockerResult struct { 97 | Error error 98 | Action string 99 | ContainerId string 100 | Result string 101 | } 102 | 103 | func (d *Docker) Run() DockerResult { 104 | ctx := context.Background() 105 | reader, err := d.Client.ImagePull(ctx, d.Config.Image, types.ImagePullOptions{}) 106 | if err != nil { 107 | log.Printf("Error pulling image %s: %v\n", d.Config.Image, err) 108 | return DockerResult{Error: err} 109 | } 110 | io.Copy(os.Stdout, reader) 111 | 112 | rp := container.RestartPolicy{ 113 | Name: d.Config.RestartPolicy, 114 | } 115 | 116 | r := container.Resources{ 117 | Memory: d.Config.Memory, 118 | NanoCPUs: int64(d.Config.Cpu * math.Pow(10, 9)), 119 | } 120 | 121 | hc := container.HostConfig{ 122 | RestartPolicy: rp, 123 | Resources: r, 124 | PublishAllPorts: true, 125 | } 126 | 127 | resp, err := d.Client.ContainerCreate(ctx, &container.Config{ 128 | Image: d.Config.Image, 129 | Tty: false, 130 | Env: d.Config.Env, 131 | ExposedPorts: d.Config.ExposedPorts, 132 | }, &hc, nil, nil, d.Config.Name) 133 | if err != nil { 134 | log.Printf("Error creating container using image %s: %v\n", d.Config.Image, err) 135 | return DockerResult{Error: err} 136 | } 137 | 138 | if err := d.Client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { 139 | log.Printf("Error starting container %s: %v\n", resp.ID, err) 140 | return DockerResult{Error: err} 141 | } 142 | 143 | out, err := d.Client.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) 144 | if err != nil { 145 | log.Printf("Error getting logs for container %s: %v\n", resp.ID, err) 146 | return DockerResult{Error: err} 147 | } 148 | 149 | stdcopy.StdCopy(os.Stdout, os.Stderr, out) 150 | 151 | return DockerResult{ContainerId: resp.ID, Action: "start", Result: "success"} 152 | } 153 | 154 | func (d *Docker) Stop(id string) DockerResult { 155 | log.Printf("Attempting to stop container %v", id) 156 | ctx := context.Background() 157 | err := d.Client.ContainerStop(ctx, id, nil) 158 | if err != nil { 159 | log.Printf("Error stopping container %s: %v\n", id, err) 160 | return DockerResult{Error: err} 161 | } 162 | 163 | err = d.Client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{ 164 | RemoveVolumes: true, 165 | RemoveLinks: false, 166 | Force: false, 167 | }) 168 | if err != nil { 169 | log.Printf("Error removing container %s: %v\n", id, err) 170 | return DockerResult{Error: err} 171 | } 172 | 173 | return DockerResult{Action: "stop", Result: "success", Error: nil} 174 | } 175 | -------------------------------------------------------------------------------- /ch4/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "math" 7 | "os" 8 | "time" 9 | 10 | "context" 11 | 12 | "github.com/docker/docker/api/types" 13 | "github.com/docker/docker/api/types/container" 14 | "github.com/docker/docker/client" 15 | "github.com/docker/go-connections/nat" 16 | "github.com/google/uuid" 17 | "github.com/moby/moby/pkg/stdcopy" 18 | ) 19 | 20 | type Task struct { 21 | ID uuid.UUID 22 | ContainerID string 23 | Name string 24 | State State 25 | Image string 26 | Cpu float64 27 | Memory int64 28 | Disk int64 29 | ExposedPorts nat.PortSet 30 | PortBindings map[string]string 31 | RestartPolicy string 32 | StartTime time.Time 33 | FinishTime time.Time 34 | } 35 | 36 | type TaskEvent struct { 37 | ID uuid.UUID 38 | State State 39 | Timestamp time.Time 40 | Task Task 41 | } 42 | 43 | // Config struct to hold Docker container config 44 | type Config struct { 45 | // Name of the task, also used as the container name 46 | Name string 47 | // AttachStdin boolean which determines if stdin should be attached 48 | AttachStdin bool 49 | // AttachStdout boolean which determines if stdout should be attached 50 | AttachStdout bool 51 | // AttachStderr boolean which determines if stderr should be attached 52 | AttachStderr bool 53 | // ExposedPorts list of ports exposed 54 | ExposedPorts nat.PortSet 55 | // Cmd to be run inside container (optional) 56 | Cmd []string 57 | // Image used to run the container 58 | Image string 59 | // Cpu 60 | Cpu float64 61 | // Memory in MiB 62 | Memory int64 63 | // Disk in GiB 64 | Disk int64 65 | // Env variables 66 | Env []string 67 | // RestartPolicy for the container ["", "always", "unless-stopped", "on-failure"] 68 | RestartPolicy string 69 | } 70 | 71 | func NewConfig(t *Task) *Config { 72 | return &Config{ 73 | Name: t.Name, 74 | ExposedPorts: t.ExposedPorts, 75 | Image: t.Image, 76 | Cpu: t.Cpu, 77 | Memory: t.Memory, 78 | Disk: t.Disk, 79 | RestartPolicy: t.RestartPolicy, 80 | } 81 | } 82 | 83 | type Docker struct { 84 | Client *client.Client 85 | Config Config 86 | } 87 | 88 | func NewDocker(c *Config) *Docker { 89 | dc, _ := client.NewClientWithOpts(client.FromEnv) 90 | return &Docker{ 91 | Client: dc, 92 | Config: *c, 93 | } 94 | } 95 | 96 | type DockerResult struct { 97 | Error error 98 | Action string 99 | ContainerId string 100 | Result string 101 | } 102 | 103 | func (d *Docker) Run() DockerResult { 104 | ctx := context.Background() 105 | reader, err := d.Client.ImagePull(ctx, d.Config.Image, types.ImagePullOptions{}) 106 | if err != nil { 107 | log.Printf("Error pulling image %s: %v\n", d.Config.Image, err) 108 | return DockerResult{Error: err} 109 | } 110 | io.Copy(os.Stdout, reader) 111 | 112 | rp := container.RestartPolicy{ 113 | Name: d.Config.RestartPolicy, 114 | } 115 | 116 | r := container.Resources{ 117 | Memory: d.Config.Memory, 118 | NanoCPUs: int64(d.Config.Cpu * math.Pow(10, 9)), 119 | } 120 | 121 | hc := container.HostConfig{ 122 | RestartPolicy: rp, 123 | Resources: r, 124 | PublishAllPorts: true, 125 | } 126 | 127 | resp, err := d.Client.ContainerCreate(ctx, &container.Config{ 128 | Image: d.Config.Image, 129 | Tty: false, 130 | Env: d.Config.Env, 131 | ExposedPorts: d.Config.ExposedPorts, 132 | }, &hc, nil, nil, d.Config.Name) 133 | if err != nil { 134 | log.Printf("Error creating container using image %s: %v\n", d.Config.Image, err) 135 | return DockerResult{Error: err} 136 | } 137 | 138 | if err := d.Client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { 139 | log.Printf("Error starting container %s: %v\n", resp.ID, err) 140 | return DockerResult{Error: err} 141 | } 142 | 143 | out, err := d.Client.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) 144 | if err != nil { 145 | log.Printf("Error getting logs for container %s: %v\n", resp.ID, err) 146 | return DockerResult{Error: err} 147 | } 148 | 149 | stdcopy.StdCopy(os.Stdout, os.Stderr, out) 150 | 151 | return DockerResult{ContainerId: resp.ID, Action: "start", Result: "success"} 152 | } 153 | 154 | func (d *Docker) Stop(id string) DockerResult { 155 | log.Printf("Attempting to stop container %v", id) 156 | ctx := context.Background() 157 | err := d.Client.ContainerStop(ctx, id, nil) 158 | if err != nil { 159 | log.Printf("Error stopping container %s: %v\n", id, err) 160 | return DockerResult{Error: err} 161 | } 162 | 163 | err = d.Client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{ 164 | RemoveVolumes: true, 165 | RemoveLinks: false, 166 | Force: false, 167 | }) 168 | if err != nil { 169 | log.Printf("Error removing container %s: %v\n", id, err) 170 | return DockerResult{Error: err} 171 | } 172 | 173 | return DockerResult{Action: "stop", Result: "success", Error: nil} 174 | } 175 | -------------------------------------------------------------------------------- /ch5/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "math" 7 | "os" 8 | "time" 9 | 10 | "context" 11 | 12 | "github.com/docker/docker/api/types" 13 | "github.com/docker/docker/api/types/container" 14 | "github.com/docker/docker/client" 15 | "github.com/docker/go-connections/nat" 16 | "github.com/google/uuid" 17 | "github.com/moby/moby/pkg/stdcopy" 18 | ) 19 | 20 | type Task struct { 21 | ID uuid.UUID 22 | ContainerID string 23 | Name string 24 | State State 25 | Image string 26 | Cpu float64 27 | Memory int64 28 | Disk int64 29 | ExposedPorts nat.PortSet 30 | PortBindings map[string]string 31 | RestartPolicy string 32 | StartTime time.Time 33 | FinishTime time.Time 34 | } 35 | 36 | type TaskEvent struct { 37 | ID uuid.UUID 38 | State State 39 | Timestamp time.Time 40 | Task Task 41 | } 42 | 43 | // Config struct to hold Docker container config 44 | type Config struct { 45 | // Name of the task, also used as the container name 46 | Name string 47 | // AttachStdin boolean which determines if stdin should be attached 48 | AttachStdin bool 49 | // AttachStdout boolean which determines if stdout should be attached 50 | AttachStdout bool 51 | // AttachStderr boolean which determines if stderr should be attached 52 | AttachStderr bool 53 | // ExposedPorts list of ports exposed 54 | ExposedPorts nat.PortSet 55 | // Cmd to be run inside container (optional) 56 | Cmd []string 57 | // Image used to run the container 58 | Image string 59 | // Cpu 60 | Cpu float64 61 | // Memory in MiB 62 | Memory int64 63 | // Disk in GiB 64 | Disk int64 65 | // Env variables 66 | Env []string 67 | // RestartPolicy for the container ["", "always", "unless-stopped", "on-failure"] 68 | RestartPolicy string 69 | } 70 | 71 | func NewConfig(t *Task) *Config { 72 | return &Config{ 73 | Name: t.Name, 74 | ExposedPorts: t.ExposedPorts, 75 | Image: t.Image, 76 | Cpu: t.Cpu, 77 | Memory: t.Memory, 78 | Disk: t.Disk, 79 | RestartPolicy: t.RestartPolicy, 80 | } 81 | } 82 | 83 | type Docker struct { 84 | Client *client.Client 85 | Config Config 86 | } 87 | 88 | func NewDocker(c *Config) *Docker { 89 | dc, _ := client.NewClientWithOpts(client.FromEnv) 90 | return &Docker{ 91 | Client: dc, 92 | Config: *c, 93 | } 94 | } 95 | 96 | type DockerResult struct { 97 | Error error 98 | Action string 99 | ContainerId string 100 | Result string 101 | } 102 | 103 | func (d *Docker) Run() DockerResult { 104 | ctx := context.Background() 105 | reader, err := d.Client.ImagePull(ctx, d.Config.Image, types.ImagePullOptions{}) 106 | if err != nil { 107 | log.Printf("Error pulling image %s: %v\n", d.Config.Image, err) 108 | return DockerResult{Error: err} 109 | } 110 | io.Copy(os.Stdout, reader) 111 | 112 | rp := container.RestartPolicy{ 113 | Name: d.Config.RestartPolicy, 114 | } 115 | 116 | r := container.Resources{ 117 | Memory: d.Config.Memory, 118 | NanoCPUs: int64(d.Config.Cpu * math.Pow(10, 9)), 119 | } 120 | 121 | hc := container.HostConfig{ 122 | RestartPolicy: rp, 123 | Resources: r, 124 | PublishAllPorts: true, 125 | } 126 | 127 | resp, err := d.Client.ContainerCreate(ctx, &container.Config{ 128 | Image: d.Config.Image, 129 | Tty: false, 130 | Env: d.Config.Env, 131 | ExposedPorts: d.Config.ExposedPorts, 132 | }, &hc, nil, nil, d.Config.Name) 133 | if err != nil { 134 | log.Printf("Error creating container using image %s: %v\n", d.Config.Image, err) 135 | return DockerResult{Error: err} 136 | } 137 | 138 | if err := d.Client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { 139 | log.Printf("Error starting container %s: %v\n", resp.ID, err) 140 | return DockerResult{Error: err} 141 | } 142 | 143 | out, err := d.Client.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) 144 | if err != nil { 145 | log.Printf("Error getting logs for container %s: %v\n", resp.ID, err) 146 | return DockerResult{Error: err} 147 | } 148 | 149 | stdcopy.StdCopy(os.Stdout, os.Stderr, out) 150 | 151 | return DockerResult{ContainerId: resp.ID, Action: "start", Result: "success"} 152 | } 153 | 154 | func (d *Docker) Stop(id string) DockerResult { 155 | log.Printf("Attempting to stop container %v", id) 156 | ctx := context.Background() 157 | err := d.Client.ContainerStop(ctx, id, nil) 158 | if err != nil { 159 | log.Printf("Error stopping container %s: %v\n", id, err) 160 | return DockerResult{Error: err} 161 | } 162 | 163 | err = d.Client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{ 164 | RemoveVolumes: true, 165 | RemoveLinks: false, 166 | Force: false, 167 | }) 168 | if err != nil { 169 | log.Printf("Error removing container %s: %v\n", id, err) 170 | return DockerResult{Error: err} 171 | } 172 | 173 | return DockerResult{Action: "stop", Result: "success", Error: nil} 174 | } 175 | -------------------------------------------------------------------------------- /ch6/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "math" 7 | "os" 8 | "time" 9 | 10 | "context" 11 | 12 | "github.com/docker/docker/api/types" 13 | "github.com/docker/docker/api/types/container" 14 | "github.com/docker/docker/client" 15 | "github.com/docker/go-connections/nat" 16 | "github.com/google/uuid" 17 | "github.com/moby/moby/pkg/stdcopy" 18 | ) 19 | 20 | type Task struct { 21 | ID uuid.UUID 22 | ContainerID string 23 | Name string 24 | State State 25 | Image string 26 | Cpu float64 27 | Memory int64 28 | Disk int64 29 | ExposedPorts nat.PortSet 30 | PortBindings map[string]string 31 | RestartPolicy string 32 | StartTime time.Time 33 | FinishTime time.Time 34 | } 35 | 36 | type TaskEvent struct { 37 | ID uuid.UUID 38 | State State 39 | Timestamp time.Time 40 | Task Task 41 | } 42 | 43 | // Config struct to hold Docker container config 44 | type Config struct { 45 | // Name of the task, also used as the container name 46 | Name string 47 | // AttachStdin boolean which determines if stdin should be attached 48 | AttachStdin bool 49 | // AttachStdout boolean which determines if stdout should be attached 50 | AttachStdout bool 51 | // AttachStderr boolean which determines if stderr should be attached 52 | AttachStderr bool 53 | // ExposedPorts list of ports exposed 54 | ExposedPorts nat.PortSet 55 | // Cmd to be run inside container (optional) 56 | Cmd []string 57 | // Image used to run the container 58 | Image string 59 | // Cpu 60 | Cpu float64 61 | // Memory in MiB 62 | Memory int64 63 | // Disk in GiB 64 | Disk int64 65 | // Env variables 66 | Env []string 67 | // RestartPolicy for the container ["", "always", "unless-stopped", "on-failure"] 68 | RestartPolicy string 69 | } 70 | 71 | func NewConfig(t *Task) *Config { 72 | return &Config{ 73 | Name: t.Name, 74 | ExposedPorts: t.ExposedPorts, 75 | Image: t.Image, 76 | Cpu: t.Cpu, 77 | Memory: t.Memory, 78 | Disk: t.Disk, 79 | RestartPolicy: t.RestartPolicy, 80 | } 81 | } 82 | 83 | type Docker struct { 84 | Client *client.Client 85 | Config Config 86 | } 87 | 88 | func NewDocker(c *Config) *Docker { 89 | dc, _ := client.NewClientWithOpts(client.FromEnv) 90 | return &Docker{ 91 | Client: dc, 92 | Config: *c, 93 | } 94 | } 95 | 96 | type DockerResult struct { 97 | Error error 98 | Action string 99 | ContainerId string 100 | Result string 101 | } 102 | 103 | func (d *Docker) Run() DockerResult { 104 | ctx := context.Background() 105 | reader, err := d.Client.ImagePull(ctx, d.Config.Image, types.ImagePullOptions{}) 106 | if err != nil { 107 | log.Printf("Error pulling image %s: %v\n", d.Config.Image, err) 108 | return DockerResult{Error: err} 109 | } 110 | io.Copy(os.Stdout, reader) 111 | 112 | rp := container.RestartPolicy{ 113 | Name: d.Config.RestartPolicy, 114 | } 115 | 116 | r := container.Resources{ 117 | Memory: d.Config.Memory, 118 | NanoCPUs: int64(d.Config.Cpu * math.Pow(10, 9)), 119 | } 120 | 121 | hc := container.HostConfig{ 122 | RestartPolicy: rp, 123 | Resources: r, 124 | PublishAllPorts: true, 125 | } 126 | 127 | resp, err := d.Client.ContainerCreate(ctx, &container.Config{ 128 | Image: d.Config.Image, 129 | Tty: false, 130 | Env: d.Config.Env, 131 | ExposedPorts: d.Config.ExposedPorts, 132 | }, &hc, nil, nil, d.Config.Name) 133 | if err != nil { 134 | log.Printf("Error creating container using image %s: %v\n", d.Config.Image, err) 135 | return DockerResult{Error: err} 136 | } 137 | 138 | if err := d.Client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { 139 | log.Printf("Error starting container %s: %v\n", resp.ID, err) 140 | return DockerResult{Error: err} 141 | } 142 | 143 | out, err := d.Client.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) 144 | if err != nil { 145 | log.Printf("Error getting logs for container %s: %v\n", resp.ID, err) 146 | return DockerResult{Error: err} 147 | } 148 | 149 | stdcopy.StdCopy(os.Stdout, os.Stderr, out) 150 | 151 | return DockerResult{ContainerId: resp.ID, Action: "start", Result: "success"} 152 | } 153 | 154 | func (d *Docker) Stop(id string) DockerResult { 155 | log.Printf("Attempting to stop container %v", id) 156 | ctx := context.Background() 157 | err := d.Client.ContainerStop(ctx, id, nil) 158 | if err != nil { 159 | log.Printf("Error stopping container %s: %v\n", id, err) 160 | return DockerResult{Error: err} 161 | } 162 | 163 | err = d.Client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{ 164 | RemoveVolumes: true, 165 | RemoveLinks: false, 166 | Force: false, 167 | }) 168 | if err != nil { 169 | log.Printf("Error removing container %s: %v\n", id, err) 170 | return DockerResult{Error: err} 171 | } 172 | 173 | return DockerResult{Action: "stop", Result: "success", Error: nil} 174 | } 175 | --------------------------------------------------------------------------------