From 9dc8757fbfb7a0519e0e6e3b8ad3590174e95833 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 09:38:56 +0100 Subject: [PATCH 01/83] use helper to generate hex string --- go.mod | 3 +++ main.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 go.mod create mode 100644 main.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..19cc9c9 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/NickGowdy/deveui-cli + +go 1.19 diff --git a/main.go b/main.go new file mode 100644 index 0000000..0fd5fb4 --- /dev/null +++ b/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "crypto/rand" + "fmt" + "log" + "math/big" +) + +const allowedChars = "ABCDEF0123456789" + +func main() { + hexStr, err := generateHexString(16) + + if err != nil { + log.Print(err) + } + + fmt.Print(hexStr) +} + +func generateHexString(length int) (string, error) { + max := big.NewInt(int64(len(allowedChars))) + b := make([]byte, length) + for i := range b { + n, err := rand.Int(rand.Reader, max) + if err != nil { + return "", err + } + b[i] = allowedChars[n.Int64()] + } + return string(b), nil +} From 106ff9a041895bd3ab6f64fc3711617f31a84582 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 09:57:29 +0100 Subject: [PATCH 02/83] can talk to api --- main.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/main.go b/main.go index 0fd5fb4..92dc1f9 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,22 @@ package main import ( + "bytes" "crypto/rand" + "encoding/json" "fmt" "log" "math/big" + "net/http" + "time" ) const allowedChars = "ABCDEF0123456789" +type Request struct { + Deveui string `json:"deveui"` +} + func main() { hexStr, err := generateHexString(16) @@ -17,6 +25,25 @@ func main() { } fmt.Print(hexStr) + + client := http.Client{Timeout: time.Duration(time.Second * time.Duration(30))} + b := new(bytes.Buffer) + + reqBody := Request{Deveui: hexStr} + + err = json.NewEncoder(b).Encode(&reqBody) + + if err != nil { + log.Print(err) + } + + resp, err := client.Post("http://europe-west1-machinemax-dev-d524.cloudfunctions.net/sensor-onboarding-sample", "application/json", b) + + if err != nil { + log.Print(err) + } + + fmt.Print(resp) } func generateHexString(length int) (string, error) { From 828bffaf0af49d0b268a048c6054c4a81cfc4760 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 10:01:50 +0100 Subject: [PATCH 03/83] getting 200 response --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 92dc1f9..0242f8b 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ type Request struct { func main() { hexStr, err := generateHexString(16) + code := hexStr[len(hexStr)-5:] if err != nil { log.Print(err) @@ -29,7 +30,7 @@ func main() { client := http.Client{Timeout: time.Duration(time.Second * time.Duration(30))} b := new(bytes.Buffer) - reqBody := Request{Deveui: hexStr} + reqBody := Request{Deveui: code} err = json.NewEncoder(b).Encode(&reqBody) From 8d6431b019471809b2750b54e191c6808883e2d6 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 10:20:43 +0100 Subject: [PATCH 04/83] WIP - basic version working --- main.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 0242f8b..7c08b3c 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "bytes" "crypto/rand" "encoding/json" - "fmt" + "io" "log" "math/big" "net/http" @@ -17,6 +17,10 @@ type Request struct { Deveui string `json:"deveui"` } +type Response struct { + Description string `json:"description"` +} + func main() { hexStr, err := generateHexString(16) code := hexStr[len(hexStr)-5:] @@ -25,8 +29,6 @@ func main() { log.Print(err) } - fmt.Print(hexStr) - client := http.Client{Timeout: time.Duration(time.Second * time.Duration(30))} b := new(bytes.Buffer) @@ -39,12 +41,24 @@ func main() { } resp, err := client.Post("http://europe-west1-machinemax-dev-d524.cloudfunctions.net/sensor-onboarding-sample", "application/json", b) + if err != nil { + log.Print(err) + } + defer resp.Body.Close() + bodyBytes, err := io.ReadAll(resp.Body) if err != nil { log.Print(err) } - fmt.Print(resp) + if resp.StatusCode == http.StatusUnprocessableEntity || resp.StatusCode == http.StatusOK { + bodyString := string(bodyBytes) + log.Print(bodyString) + } + + if err != nil { + log.Print(err) + } } func generateHexString(length int) (string, error) { From 577384b95fb9551457722e17e89695b3940e75de Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 11:05:33 +0100 Subject: [PATCH 05/83] move http client code to separate module --- client/httpclient.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 client/httpclient.go diff --git a/client/httpclient.go b/client/httpclient.go new file mode 100644 index 0000000..818fa7b --- /dev/null +++ b/client/httpclient.go @@ -0,0 +1,33 @@ +package client + +import ( + "bytes" + "fmt" + "net/http" + "time" +) + +type HttpClient struct { + timeout time.Duration + baseUrl string + client http.Client + contentType string +} + +func NewHttpClient(duration time.Duration, baseUrl string) *HttpClient { + return &HttpClient{ + timeout: duration, + baseUrl: baseUrl, + } +} + +func (h *HttpClient) Post(endpoint string, b *bytes.Buffer) (*http.Response, error) { + url := fmt.Sprintf("%s/%s", h.baseUrl, endpoint) + resp, err := h.client.Post(url, h.contentType, b) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + return resp, nil +} From eb087830fe6d90825c316c4746b532492f780d87 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 11:41:30 +0100 Subject: [PATCH 06/83] WIP - works but need to keep track of succesful registration --- main.go | 72 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/main.go b/main.go index 7c08b3c..5786daf 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,12 @@ import ( "bytes" "crypto/rand" "encoding/json" - "io" + "fmt" "log" "math/big" - "net/http" "time" + + "github.com/NickGowdy/deveui-cli/client" ) const allowedChars = "ABCDEF0123456789" @@ -17,48 +18,73 @@ type Request struct { Deveui string `json:"deveui"` } -type Response struct { - Description string `json:"description"` +type Message struct { + Code string + Status string +} + +type Server struct { + msgch chan Message + quitch chan struct{} +} + +func (s *Server) StartAndListen() { + for { + select { + // block here until someone is sending a message to the channel + case msg := <-s.msgch: + fmt.Printf("code: %s with status: %s\n", msg.Code, msg.Status) + case <-s.quitch: + default: + + } + } } func main() { - hexStr, err := generateHexString(16) - code := hexStr[len(hexStr)-5:] - if err != nil { - log.Print(err) + s := &Server{ + msgch: make(chan Message, 10), } + client := client.NewHttpClient(30, "http://europe-west1-machinemax-dev-d524.cloudfunctions.net") - client := http.Client{Timeout: time.Duration(time.Second * time.Duration(30))} - b := new(bytes.Buffer) + go s.StartAndListen() + // var i int + for { + time.Sleep(2000 * time.Millisecond) + hexStr, err := generateHexString(16) + if err != nil { + log.Print(err) + } - reqBody := Request{Deveui: code} + code := hexStr[len(hexStr)-5:] + go registerCode(code, client, s.msgch) + } +} - err = json.NewEncoder(b).Encode(&reqBody) +func registerCode(code string, client *client.HttpClient, msgch chan Message) { + + b := new(bytes.Buffer) + reqBody := Request{Deveui: code} + err := json.NewEncoder(b).Encode(&reqBody) if err != nil { log.Print(err) } - resp, err := client.Post("http://europe-west1-machinemax-dev-d524.cloudfunctions.net/sensor-onboarding-sample", "application/json", b) + resp, err := client.Post("sensor-onboarding-sample", b) if err != nil { log.Print(err) } defer resp.Body.Close() - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - log.Print(err) - } - if resp.StatusCode == http.StatusUnprocessableEntity || resp.StatusCode == http.StatusOK { - bodyString := string(bodyBytes) - log.Print(bodyString) + msg := Message{ + Code: code, + Status: resp.Status, } - if err != nil { - log.Print(err) - } + msgch <- msg } func generateHexString(length int) (string, error) { From b84e4d980fe3db576c630b51050b5f86da23e411 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 15:07:36 +0100 Subject: [PATCH 07/83] change setup for client --- client/httpclient.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/client/httpclient.go b/client/httpclient.go index 818fa7b..f7fd5c7 100644 --- a/client/httpclient.go +++ b/client/httpclient.go @@ -8,22 +8,20 @@ import ( ) type HttpClient struct { - timeout time.Duration - baseUrl string - client http.Client - contentType string + baseUrl string + client http.Client } func NewHttpClient(duration time.Duration, baseUrl string) *HttpClient { return &HttpClient{ - timeout: duration, + client: http.Client{Timeout: time.Duration(time.Second * time.Duration(duration))}, baseUrl: baseUrl, } } func (h *HttpClient) Post(endpoint string, b *bytes.Buffer) (*http.Response, error) { url := fmt.Sprintf("%s/%s", h.baseUrl, endpoint) - resp, err := h.client.Post(url, h.contentType, b) + resp, err := h.client.Post(url, "application/json", b) if err != nil { return nil, err } From 9f37db2120bb4709719fd07c1058b805de88074c Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 15:54:09 +0100 Subject: [PATCH 08/83] small refactor --- client/httpclient.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/client/httpclient.go b/client/httpclient.go index f7fd5c7..47cb3aa 100644 --- a/client/httpclient.go +++ b/client/httpclient.go @@ -4,24 +4,16 @@ import ( "bytes" "fmt" "net/http" - "time" ) type HttpClient struct { - baseUrl string - client http.Client -} - -func NewHttpClient(duration time.Duration, baseUrl string) *HttpClient { - return &HttpClient{ - client: http.Client{Timeout: time.Duration(time.Second * time.Duration(duration))}, - baseUrl: baseUrl, - } + BaseUrl string + Client http.Client } func (h *HttpClient) Post(endpoint string, b *bytes.Buffer) (*http.Response, error) { - url := fmt.Sprintf("%s/%s", h.baseUrl, endpoint) - resp, err := h.client.Post(url, "application/json", b) + url := fmt.Sprintf("%s/%s", h.BaseUrl, endpoint) + resp, err := h.Client.Post(url, "application/json", b) if err != nil { return nil, err } From 12f7d10d2eab89e8fe0f1fbdb02428564f7395f8 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 15:54:18 +0100 Subject: [PATCH 09/83] use lock to change iterator --- main.go | 67 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/main.go b/main.go index 5786daf..104bec9 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,8 @@ import ( "fmt" "log" "math/big" + "net/http" + "sync" "time" "github.com/NickGowdy/deveui-cli/client" @@ -24,19 +26,20 @@ type Message struct { } type Server struct { - msgch chan Message - quitch chan struct{} + msgch chan Message + quitch chan struct{} + request int } func (s *Server) StartAndListen() { +listening: for { select { - // block here until someone is sending a message to the channel case msg := <-s.msgch: fmt.Printf("code: %s with status: %s\n", msg.Code, msg.Status) case <-s.quitch: - default: - + fmt.Print("shutting down...") + break listening } } } @@ -44,25 +47,59 @@ func (s *Server) StartAndListen() { func main() { s := &Server{ - msgch: make(chan Message, 10), + msgch: make(chan Message, 10), + quitch: make(chan struct{}), + request: 100, + } + client := &client.HttpClient{ + BaseUrl: "http://europe-west1-machinemax-dev-d524.cloudfunctions.net", + Client: http.Client{Timeout: time.Duration(time.Second * time.Duration(30000))}, } - client := client.NewHttpClient(30, "http://europe-west1-machinemax-dev-d524.cloudfunctions.net") go s.StartAndListen() - // var i int + + var i int + var lock sync.Mutex + + var wg sync.WaitGroup for { - time.Sleep(2000 * time.Millisecond) + time.Sleep(500 * time.Millisecond) hexStr, err := generateHexString(16) if err != nil { log.Print(err) } code := hexStr[len(hexStr)-5:] - go registerCode(code, client, s.msgch) + wg.Add(1) + go func(code string) { + resp, err := registerCode(code, client) + if err != nil { + log.Print(err) + } + + if resp.StatusCode == 200 { + msg := Message{ + Code: code, + Status: resp.Status, + } + + s.msgch <- msg + lock.Lock() + defer lock.Unlock() + i++ + } + + if i == 2 { + close(s.quitch) + } + + wg.Done() + }(code) } + } -func registerCode(code string, client *client.HttpClient, msgch chan Message) { +func registerCode(code string, client *client.HttpClient) (*http.Response, error) { b := new(bytes.Buffer) reqBody := Request{Deveui: code} @@ -78,13 +115,7 @@ func registerCode(code string, client *client.HttpClient, msgch chan Message) { } defer resp.Body.Close() - - msg := Message{ - Code: code, - Status: resp.Status, - } - - msgch <- msg + return resp, nil } func generateHexString(length int) (string, error) { From 5571400985d609f4751e05369751c0c63ea360c9 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 16:11:34 +0100 Subject: [PATCH 10/83] file for registering code --- register/coderegister.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 register/coderegister.go diff --git a/register/coderegister.go b/register/coderegister.go new file mode 100644 index 0000000..418dd6a --- /dev/null +++ b/register/coderegister.go @@ -0,0 +1,33 @@ +package register + +import ( + "bytes" + "encoding/json" + "log" + "net/http" + + "github.com/NickGowdy/deveui-cli/client" +) + +type CodeRegister struct { + HttpClient client.HttpClient + Code string +} + +func (cr CodeRegister) RegisterCode() (*http.Response, error) { + b := new(bytes.Buffer) + reqBody := map[string]string{"Deveui": cr.Code} + + err := json.NewEncoder(b).Encode(&reqBody) + if err != nil { + log.Print(err) + } + + resp, err := cr.HttpClient.Post("sensor-onboarding-sample", b) + if err != nil { + log.Print(err) + } + + defer resp.Body.Close() + return resp, nil +} From 306c3f908b0a8f7b5103c2a394fd4f0b4669df15 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 16:30:47 +0100 Subject: [PATCH 11/83] changes and file rename --- channel/code_channel.go | 26 ++++ client/{httpclient.go => http_client.go} | 0 main.go | 122 ++---------------- processor/code_processor.go | 80 ++++++++++++ .../{coderegister.go => code_register.go} | 0 5 files changed, 118 insertions(+), 110 deletions(-) create mode 100644 channel/code_channel.go rename client/{httpclient.go => http_client.go} (100%) create mode 100644 processor/code_processor.go rename register/{coderegister.go => code_register.go} (100%) diff --git a/channel/code_channel.go b/channel/code_channel.go new file mode 100644 index 0000000..384d8fc --- /dev/null +++ b/channel/code_channel.go @@ -0,0 +1,26 @@ +package channel + +import "fmt" + +type Message struct { + Code string + Status string +} + +type CodeChannel struct { + Msgch chan Message + Quitch chan struct{} +} + +func (cc *CodeChannel) StartAndListen() { +listening: + for { + select { + case msg := <-cc.Msgch: + fmt.Printf("code: %s with status: %s\n", msg.Code, msg.Status) + case <-cc.Quitch: + fmt.Print("shutting down...") + break listening + } + } +} diff --git a/client/httpclient.go b/client/http_client.go similarity index 100% rename from client/httpclient.go rename to client/http_client.go diff --git a/main.go b/main.go index 104bec9..2be6562 100644 --- a/main.go +++ b/main.go @@ -1,132 +1,34 @@ package main import ( - "bytes" - "crypto/rand" - "encoding/json" - "fmt" - "log" - "math/big" "net/http" - "sync" "time" + "github.com/NickGowdy/deveui-cli/channel" "github.com/NickGowdy/deveui-cli/client" + "github.com/NickGowdy/deveui-cli/processor" ) -const allowedChars = "ABCDEF0123456789" - -type Request struct { - Deveui string `json:"deveui"` -} - -type Message struct { - Code string - Status string -} - -type Server struct { - msgch chan Message - quitch chan struct{} - request int -} - -func (s *Server) StartAndListen() { -listening: - for { - select { - case msg := <-s.msgch: - fmt.Printf("code: %s with status: %s\n", msg.Code, msg.Status) - case <-s.quitch: - fmt.Print("shutting down...") - break listening - } - } -} - func main() { - s := &Server{ - msgch: make(chan Message, 10), - quitch: make(chan struct{}), - request: 100, + codeChannel := &channel.CodeChannel{ + Msgch: make(chan channel.Message, 10), + Quitch: make(chan struct{}), } + client := &client.HttpClient{ BaseUrl: "http://europe-west1-machinemax-dev-d524.cloudfunctions.net", Client: http.Client{Timeout: time.Duration(time.Second * time.Duration(30000))}, } - go s.StartAndListen() - - var i int - var lock sync.Mutex - - var wg sync.WaitGroup - for { - time.Sleep(500 * time.Millisecond) - hexStr, err := generateHexString(16) - if err != nil { - log.Print(err) - } - - code := hexStr[len(hexStr)-5:] - wg.Add(1) - go func(code string) { - resp, err := registerCode(code, client) - if err != nil { - log.Print(err) - } - - if resp.StatusCode == 200 { - msg := Message{ - Code: code, - Status: resp.Status, - } - - s.msgch <- msg - lock.Lock() - defer lock.Unlock() - i++ - } - - if i == 2 { - close(s.quitch) - } - - wg.Done() - }(code) + CodeProcessor := &processor.CodeProcessor{ + Client: client, + CodeChannel: codeChannel, + RegisterNumber: 100, } -} - -func registerCode(code string, client *client.HttpClient) (*http.Response, error) { + go codeChannel.StartAndListen() - b := new(bytes.Buffer) - reqBody := Request{Deveui: code} + CodeProcessor.Process() - err := json.NewEncoder(b).Encode(&reqBody) - if err != nil { - log.Print(err) - } - - resp, err := client.Post("sensor-onboarding-sample", b) - if err != nil { - log.Print(err) - } - - defer resp.Body.Close() - return resp, nil -} - -func generateHexString(length int) (string, error) { - max := big.NewInt(int64(len(allowedChars))) - b := make([]byte, length) - for i := range b { - n, err := rand.Int(rand.Reader, max) - if err != nil { - return "", err - } - b[i] = allowedChars[n.Int64()] - } - return string(b), nil } diff --git a/processor/code_processor.go b/processor/code_processor.go new file mode 100644 index 0000000..2817af5 --- /dev/null +++ b/processor/code_processor.go @@ -0,0 +1,80 @@ +package processor + +import ( + "crypto/rand" + "log" + "math/big" + "sync" + "time" + + "github.com/NickGowdy/deveui-cli/channel" + "github.com/NickGowdy/deveui-cli/client" + "github.com/NickGowdy/deveui-cli/register" +) + +const allowedChars = "ABCDEF0123456789" + +type CodeProcessor struct { + Client *client.HttpClient + CodeChannel *channel.CodeChannel + RegisterNumber int +} + +func (cp *CodeProcessor) Process() { + var i int + var lock sync.Mutex + var wg sync.WaitGroup + for { + time.Sleep(500 * time.Millisecond) + + hexStr, err := generateHexString(16) + if err != nil { + log.Print(err) + } + code := hexStr[len(hexStr)-5:] + codeRegister := ®ister.CodeRegister{ + HttpClient: *cp.Client, + Code: code, + } + + wg.Add(1) + go func(code string) { + + resp, err := codeRegister.RegisterCode() + if err != nil { + log.Print(err) + } + + if resp.StatusCode == 200 { + msg := channel.Message{ + Code: code, + Status: resp.Status, + } + + cp.CodeChannel.Msgch <- msg + lock.Lock() + defer lock.Unlock() + i++ + } + + if i == cp.RegisterNumber { + close(cp.CodeChannel.Quitch) + } + + wg.Done() + }(code) + } +} + +func generateHexString(length int) (string, error) { + max := big.NewInt(int64(len(allowedChars))) + b := make([]byte, length) + for i := range b { + n, err := rand.Int(rand.Reader, max) + if err != nil { + return "", err + } + b[i] = allowedChars[n.Int64()] + } + return string(b), nil +} diff --git a/register/coderegister.go b/register/code_register.go similarity index 100% rename from register/coderegister.go rename to register/code_register.go From f83a4da26f937fcd649b80018daf58d4bf2f8343 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 17:24:01 +0100 Subject: [PATCH 12/83] can now listen to signal and restart --- channel/code_channel.go | 10 ++++++++-- channel/signal_channel.go | 23 +++++++++++++++++++++++ main.go | 10 +++++----- processor/code_processor.go | 10 +++++++++- 4 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 channel/signal_channel.go diff --git a/channel/code_channel.go b/channel/code_channel.go index 384d8fc..ebf296f 100644 --- a/channel/code_channel.go +++ b/channel/code_channel.go @@ -1,6 +1,10 @@ package channel -import "fmt" +import ( + "fmt" + "os" + "time" +) type Message struct { Code string @@ -19,7 +23,9 @@ listening: case msg := <-cc.Msgch: fmt.Printf("code: %s with status: %s\n", msg.Code, msg.Status) case <-cc.Quitch: - fmt.Print("shutting down...") + fmt.Print("shutting down...\n") + time.Sleep(1000) + os.Exit(4) break listening } } diff --git a/channel/signal_channel.go b/channel/signal_channel.go new file mode 100644 index 0000000..af115ed --- /dev/null +++ b/channel/signal_channel.go @@ -0,0 +1,23 @@ +package channel + +import ( + "log" + "os" + "os/signal" + "syscall" + "time" +) + +type SignalChannel struct{} + +func (sc *SignalChannel) StartAndListen() { + cancelChan := make(chan os.Signal, 1) + signal.Notify(cancelChan, syscall.SIGTERM, syscall.SIGINT) + go func() { + for { + time.Sleep(1000) + } + }() + sig := <-cancelChan + log.Printf("Caught signal %v", sig) +} diff --git a/main.go b/main.go index 2be6562..c8fa63b 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,8 @@ func main() { Quitch: make(chan struct{}), } + signalChannel := &channel.SignalChannel{} + client := &client.HttpClient{ BaseUrl: "http://europe-west1-machinemax-dev-d524.cloudfunctions.net", Client: http.Client{Timeout: time.Duration(time.Second * time.Duration(30000))}, @@ -24,11 +26,9 @@ func main() { CodeProcessor := &processor.CodeProcessor{ Client: client, CodeChannel: codeChannel, - RegisterNumber: 100, + SignalChannel: signalChannel, + RegisterNumber: 10, } - go codeChannel.StartAndListen() - - CodeProcessor.Process() - + CodeProcessor.Start() } diff --git a/processor/code_processor.go b/processor/code_processor.go index 2817af5..1926ad7 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -17,10 +17,18 @@ const allowedChars = "ABCDEF0123456789" type CodeProcessor struct { Client *client.HttpClient CodeChannel *channel.CodeChannel + SignalChannel *channel.SignalChannel RegisterNumber int } -func (cp *CodeProcessor) Process() { +func (cp *CodeProcessor) Start() { + go cp.CodeChannel.StartAndListen() + go cp.SignalChannel.StartAndListen() + + process(cp) +} + +func process(cp *CodeProcessor) { var i int var lock sync.Mutex var wg sync.WaitGroup From 615c9bb33f67b1430058e414858bcdd5994f6018 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 17:33:11 +0100 Subject: [PATCH 13/83] added env vars --- .gitignore | 3 +++ go.mod | 2 ++ go.sum | 2 ++ main.go | 23 ++++++++++++++++++++--- 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 66fd13c..807438d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# Ignore environment files +.env \ No newline at end of file diff --git a/go.mod b/go.mod index 19cc9c9..6d8725f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/NickGowdy/deveui-cli go 1.19 + +require github.com/joho/godotenv v1.5.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/main.go b/main.go index c8fa63b..9611a94 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,32 @@ package main import ( + "log" "net/http" + "os" + "strconv" "time" "github.com/NickGowdy/deveui-cli/channel" "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/processor" + "github.com/joho/godotenv" ) func main() { + godotenv.Load(".env") + + baseurl := os.Getenv("BASE_URL") + + timeout, err := strconv.Atoi(os.Getenv("TIMEOUT")) + if err != nil { + log.Fatal(err) + } + + limit, err := strconv.Atoi(os.Getenv("CODE_REGISTRATION_LIMIT")) + if err != nil { + log.Fatal(err) + } codeChannel := &channel.CodeChannel{ Msgch: make(chan channel.Message, 10), @@ -19,15 +36,15 @@ func main() { signalChannel := &channel.SignalChannel{} client := &client.HttpClient{ - BaseUrl: "http://europe-west1-machinemax-dev-d524.cloudfunctions.net", - Client: http.Client{Timeout: time.Duration(time.Second * time.Duration(30000))}, + BaseUrl: baseurl, + Client: http.Client{Timeout: time.Duration(time.Second * time.Duration(timeout))}, } CodeProcessor := &processor.CodeProcessor{ Client: client, CodeChannel: codeChannel, SignalChannel: signalChannel, - RegisterNumber: 10, + RegisterNumber: limit, } CodeProcessor.Start() From d8779ff5eafff660fe619dc498c58c55146be15e Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Wed, 19 Apr 2023 17:34:41 +0100 Subject: [PATCH 14/83] basic readme --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00f6140..d0bde06 100644 --- a/README.md +++ b/README.md @@ -1 +1,13 @@ -# deveui-cli \ No newline at end of file +# deveui-cli + +From the root run `touch .env` and add these vars: + +``` +BASE_URL=http://europe-west1-machinemax-dev-d524.cloudfunctions.net +TIMEOUT=30000 +CODE_REGISTRATION_LIMIT=100 +``` + +Then run `go run main.go` to register devices. + +More content to be added later. \ No newline at end of file From c52424d8836082a6f4850cdcd7248fdcef6cf98c Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 07:47:54 +0100 Subject: [PATCH 15/83] dont need defer here, was causing crash --- register/code_register.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/register/code_register.go b/register/code_register.go index 418dd6a..6d6031c 100644 --- a/register/code_register.go +++ b/register/code_register.go @@ -10,7 +10,7 @@ import ( ) type CodeRegister struct { - HttpClient client.HttpClient + HttpClient client.Client Code string } @@ -28,6 +28,5 @@ func (cr CodeRegister) RegisterCode() (*http.Response, error) { log.Print(err) } - defer resp.Body.Close() return resp, nil } From c50c2d46f0ab442cd2cc8ea7d72b80decfb10c91 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 07:48:15 +0100 Subject: [PATCH 16/83] use interface so we can mock http request --- client/client.go | 10 ++++++++++ processor/code_processor.go | 6 ++---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 client/client.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..a68d81c --- /dev/null +++ b/client/client.go @@ -0,0 +1,10 @@ +package client + +import ( + "bytes" + "net/http" +) + +type Client interface { + Post(endpoint string, b *bytes.Buffer) (*http.Response, error) +} diff --git a/processor/code_processor.go b/processor/code_processor.go index 1926ad7..e5bc619 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -5,7 +5,6 @@ import ( "log" "math/big" "sync" - "time" "github.com/NickGowdy/deveui-cli/channel" "github.com/NickGowdy/deveui-cli/client" @@ -15,7 +14,7 @@ import ( const allowedChars = "ABCDEF0123456789" type CodeProcessor struct { - Client *client.HttpClient + Client client.Client CodeChannel *channel.CodeChannel SignalChannel *channel.SignalChannel RegisterNumber int @@ -33,7 +32,6 @@ func process(cp *CodeProcessor) { var lock sync.Mutex var wg sync.WaitGroup for { - time.Sleep(500 * time.Millisecond) hexStr, err := generateHexString(16) if err != nil { @@ -41,7 +39,7 @@ func process(cp *CodeProcessor) { } code := hexStr[len(hexStr)-5:] codeRegister := ®ister.CodeRegister{ - HttpClient: *cp.Client, + HttpClient: cp.Client, Code: code, } From 877b7d65bb771e1392268a0e79ed07e087c73de8 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 07:48:36 +0100 Subject: [PATCH 17/83] unit tests --- processor/code_processor_test.go | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 processor/code_processor_test.go diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go new file mode 100644 index 0000000..8c0cfc1 --- /dev/null +++ b/processor/code_processor_test.go @@ -0,0 +1,44 @@ +package processor + +import ( + "bytes" + "net/http" + "testing" + + "github.com/NickGowdy/deveui-cli/channel" +) + +type MockClient struct { + DoPost func(endpoint string, b *bytes.Buffer) (*http.Response, error) +} + +func TestCanProcessCodes(t *testing.T) { + codeChannel := &channel.CodeChannel{ + Msgch: make(chan channel.Message, 10), + Quitch: make(chan struct{}), + } + + signalChannel := &channel.SignalChannel{} + + client := &MockClient{ + DoPost: func(endpoint string, b *bytes.Buffer) (*http.Response, error) { + // do whatever you want + return &http.Response{ + StatusCode: http.StatusOK, + }, nil + }, + } + + CodeProcessor := &CodeProcessor{ + Client: client, + CodeChannel: codeChannel, + SignalChannel: signalChannel, + RegisterNumber: 10, + } + + CodeProcessor.Start() +} + +func (m *MockClient) Post(endpoint string, b *bytes.Buffer) (*http.Response, error) { + return &http.Response{}, nil +} From 18423d8f0adea224c9ef3288cdd6c5cf00d55fd2 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 07:48:47 +0100 Subject: [PATCH 18/83] changed to direct because of linting error --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6d8725f..aa4b41a 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/NickGowdy/deveui-cli go 1.19 -require github.com/joho/godotenv v1.5.1 // indirect +require github.com/joho/godotenv v1.5.1 // direct From 064cdb70401c5a3b0dc1c9337a319438bc6df557 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 09:41:01 +0100 Subject: [PATCH 19/83] bug fixing --- client/http_client.go | 1 - processor/code_processor.go | 4 ---- processor/code_processor_test.go | 19 ++++++++++++++----- register/code_register.go | 2 ++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/client/http_client.go b/client/http_client.go index 47cb3aa..818d1f5 100644 --- a/client/http_client.go +++ b/client/http_client.go @@ -18,6 +18,5 @@ func (h *HttpClient) Post(endpoint string, b *bytes.Buffer) (*http.Response, err return nil, err } - defer resp.Body.Close() return resp, nil } diff --git a/processor/code_processor.go b/processor/code_processor.go index e5bc619..c4a465d 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -30,7 +30,6 @@ func (cp *CodeProcessor) Start() { func process(cp *CodeProcessor) { var i int var lock sync.Mutex - var wg sync.WaitGroup for { hexStr, err := generateHexString(16) @@ -43,7 +42,6 @@ func process(cp *CodeProcessor) { Code: code, } - wg.Add(1) go func(code string) { resp, err := codeRegister.RegisterCode() @@ -66,8 +64,6 @@ func process(cp *CodeProcessor) { if i == cp.RegisterNumber { close(cp.CodeChannel.Quitch) } - - wg.Done() }(code) } } diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 8c0cfc1..9041567 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -1,8 +1,12 @@ package processor import ( + "bufio" "bytes" + "fmt" + "io/ioutil" "net/http" + "os" "testing" "github.com/NickGowdy/deveui-cli/channel" @@ -22,10 +26,7 @@ func TestCanProcessCodes(t *testing.T) { client := &MockClient{ DoPost: func(endpoint string, b *bytes.Buffer) (*http.Response, error) { - // do whatever you want - return &http.Response{ - StatusCode: http.StatusOK, - }, nil + return &http.Response{}, nil }, } @@ -36,9 +37,17 @@ func TestCanProcessCodes(t *testing.T) { RegisterNumber: 10, } + reader := bufio.NewReader(os.Stdin) CodeProcessor.Start() + + text, _ := reader.ReadString('\n') + fmt.Println(text) } func (m *MockClient) Post(endpoint string, b *bytes.Buffer) (*http.Response, error) { - return &http.Response{}, nil + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(nil)), + Status: "200 OK"}, + nil } diff --git a/register/code_register.go b/register/code_register.go index 6d6031c..f6462ed 100644 --- a/register/code_register.go +++ b/register/code_register.go @@ -28,5 +28,7 @@ func (cr CodeRegister) RegisterCode() (*http.Response, error) { log.Print(err) } + defer resp.Body.Close() + return resp, nil } From 95630cfc988f1128d44a40f6bff0f793c8c9a3e4 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 11:33:56 +0100 Subject: [PATCH 20/83] Seems more stable --- channel/code_channel.go | 3 +-- client/client.go | 4 ++-- client/http_client.go | 10 +++++----- main.go | 8 +++++++- processor/code_processor.go | 22 +++++++++++++--------- processor/code_processor_test.go | 10 +++++----- register/code_register.go | 4 +--- 7 files changed, 34 insertions(+), 27 deletions(-) diff --git a/channel/code_channel.go b/channel/code_channel.go index ebf296f..f3d2e3f 100644 --- a/channel/code_channel.go +++ b/channel/code_channel.go @@ -2,7 +2,6 @@ package channel import ( "fmt" - "os" "time" ) @@ -25,7 +24,7 @@ listening: case <-cc.Quitch: fmt.Print("shutting down...\n") time.Sleep(1000) - os.Exit(4) + // os.Exit(4) break listening } } diff --git a/client/client.go b/client/client.go index a68d81c..05e0656 100644 --- a/client/client.go +++ b/client/client.go @@ -1,10 +1,10 @@ package client import ( - "bytes" + "io" "net/http" ) type Client interface { - Post(endpoint string, b *bytes.Buffer) (*http.Response, error) + Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) } diff --git a/client/http_client.go b/client/http_client.go index 818d1f5..b054c13 100644 --- a/client/http_client.go +++ b/client/http_client.go @@ -1,19 +1,19 @@ package client import ( - "bytes" "fmt" + "io" "net/http" ) type HttpClient struct { BaseUrl string - Client http.Client + Client Client } -func (h *HttpClient) Post(endpoint string, b *bytes.Buffer) (*http.Response, error) { - url := fmt.Sprintf("%s/%s", h.BaseUrl, endpoint) - resp, err := h.Client.Post(url, "application/json", b) +func (h *HttpClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + fullUrl := fmt.Sprintf("%s/%s", h.BaseUrl, url) + resp, err = h.Client.Post(fullUrl, contentType, body) if err != nil { return nil, err } diff --git a/main.go b/main.go index 9611a94..fe464c0 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,13 @@ func main() { client := &client.HttpClient{ BaseUrl: baseurl, - Client: http.Client{Timeout: time.Duration(time.Second * time.Duration(timeout))}, + Client: &http.Client{ + Timeout: time.Duration(time.Second * time.Duration(timeout)), + Transport: &http.Transport{ + MaxIdleConns: 10, + MaxIdleConnsPerHost: 10, + }, + }, } CodeProcessor := &processor.CodeProcessor{ diff --git a/processor/code_processor.go b/processor/code_processor.go index c4a465d..3ec2b99 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -30,7 +30,8 @@ func (cp *CodeProcessor) Start() { func process(cp *CodeProcessor) { var i int var lock sync.Mutex - for { + var wg = &sync.WaitGroup{} + for i < cp.RegisterNumber { hexStr, err := generateHexString(16) if err != nil { @@ -41,7 +42,7 @@ func process(cp *CodeProcessor) { HttpClient: cp.Client, Code: code, } - + wg.Add(1) go func(code string) { resp, err := codeRegister.RegisterCode() @@ -49,23 +50,26 @@ func process(cp *CodeProcessor) { log.Print(err) } - if resp.StatusCode == 200 { + if resp != nil { + defer resp.Body.Close() + msg := channel.Message{ Code: code, Status: resp.Status, } cp.CodeChannel.Msgch <- msg - lock.Lock() - defer lock.Unlock() - i++ + if resp.StatusCode == 200 { + lock.Lock() + defer lock.Unlock() + i++ + } } - if i == cp.RegisterNumber { - close(cp.CodeChannel.Quitch) - } }(code) + wg.Done() } + close(cp.CodeChannel.Quitch) } func generateHexString(length int) (string, error) { diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 9041567..d6bbed4 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -4,7 +4,7 @@ import ( "bufio" "bytes" "fmt" - "io/ioutil" + "io" "net/http" "os" "testing" @@ -13,7 +13,7 @@ import ( ) type MockClient struct { - DoPost func(endpoint string, b *bytes.Buffer) (*http.Response, error) + DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) } func TestCanProcessCodes(t *testing.T) { @@ -25,7 +25,7 @@ func TestCanProcessCodes(t *testing.T) { signalChannel := &channel.SignalChannel{} client := &MockClient{ - DoPost: func(endpoint string, b *bytes.Buffer) (*http.Response, error) { + DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { return &http.Response{}, nil }, } @@ -44,10 +44,10 @@ func TestCanProcessCodes(t *testing.T) { fmt.Println(text) } -func (m *MockClient) Post(endpoint string, b *bytes.Buffer) (*http.Response, error) { +func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { return &http.Response{ StatusCode: http.StatusOK, - Body: ioutil.NopCloser(bytes.NewReader(nil)), + Body: io.NopCloser(bytes.NewReader(nil)), Status: "200 OK"}, nil } diff --git a/register/code_register.go b/register/code_register.go index f6462ed..e88d2df 100644 --- a/register/code_register.go +++ b/register/code_register.go @@ -23,12 +23,10 @@ func (cr CodeRegister) RegisterCode() (*http.Response, error) { log.Print(err) } - resp, err := cr.HttpClient.Post("sensor-onboarding-sample", b) + resp, err := cr.HttpClient.Post("sensor-onboarding-sample", "application/json", b) if err != nil { log.Print(err) } - defer resp.Body.Close() - return resp, nil } From 8e823cc74452be7b00bfcede9ff72b883fbfbf6a Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 13:25:17 +0100 Subject: [PATCH 21/83] WIP - seems to work better this way --- main.go | 136 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 35 deletions(-) diff --git a/main.go b/main.go index fe464c0..ba5c1c5 100644 --- a/main.go +++ b/main.go @@ -1,57 +1,123 @@ package main import ( + "bytes" + "crypto/rand" + "encoding/json" + "fmt" "log" + "math/big" "net/http" - "os" - "strconv" + "sync" + "sync/atomic" "time" - - "github.com/NickGowdy/deveui-cli/channel" - "github.com/NickGowdy/deveui-cli/client" - "github.com/NickGowdy/deveui-cli/processor" - "github.com/joho/godotenv" ) +const MAX_CONCURRENT_JOBS = 10 +const allowedChars = "ABCDEF0123456789" + func main() { - godotenv.Load(".env") + waitChan := make(chan struct{}, MAX_CONCURRENT_JOBS) + var count int32 + var wg sync.WaitGroup - baseurl := os.Getenv("BASE_URL") + for count < 100 { + wg.Add(1) + waitChan <- struct{}{} - timeout, err := strconv.Atoi(os.Getenv("TIMEOUT")) - if err != nil { - log.Fatal(err) + go func(ops int32) { + saved := job() + if saved { + atomic.AddInt32(&count, 1) + } + + <-waitChan + wg.Done() + }(count) } - limit, err := strconv.Atoi(os.Getenv("CODE_REGISTRATION_LIMIT")) + close(waitChan) +} + +func job() bool { + hexStr, err := generateHexString(16) if err != nil { - log.Fatal(err) + log.Print(err) } + code := hexStr[len(hexStr)-5:] + client := http.Client{Timeout: time.Second * 30} - codeChannel := &channel.CodeChannel{ - Msgch: make(chan channel.Message, 10), - Quitch: make(chan struct{}), - } + b := new(bytes.Buffer) + reqBody := map[string]string{"Deveui": code} - signalChannel := &channel.SignalChannel{} - - client := &client.HttpClient{ - BaseUrl: baseurl, - Client: &http.Client{ - Timeout: time.Duration(time.Second * time.Duration(timeout)), - Transport: &http.Transport{ - MaxIdleConns: 10, - MaxIdleConnsPerHost: 10, - }, - }, + err = json.NewEncoder(b).Encode(&reqBody) + if err != nil { + log.Print(err) } - CodeProcessor := &processor.CodeProcessor{ - Client: client, - CodeChannel: codeChannel, - SignalChannel: signalChannel, - RegisterNumber: limit, + resp, err := client.Post("http://europe-west1-machinemax-dev-d524.cloudfunctions.net/sensor-onboarding-sample", "application/json", b) + + if err != nil { + log.Print(err) } - CodeProcessor.Start() + defer resp.Body.Close() + + fmt.Printf("%s\n", resp.Status) + + return resp.StatusCode == http.StatusOK } + +func generateHexString(length int) (string, error) { + max := big.NewInt(int64(len(allowedChars))) + b := make([]byte, length) + for i := range b { + n, err := rand.Int(rand.Reader, max) + if err != nil { + return "", err + } + b[i] = allowedChars[n.Int64()] + } + return string(b), nil +} + +// godotenv.Load(".env") + +// baseurl := os.Getenv("BASE_URL") + +// timeout, err := strconv.Atoi(os.Getenv("TIMEOUT")) +// if err != nil { +// log.Fatal(err) +// } + +// limit, err := strconv.Atoi(os.Getenv("CODE_REGISTRATION_LIMIT")) +// if err != nil { +// log.Fatal(err) +// } + +// codeChannel := &channel.CodeChannel{ +// Msgch: make(chan channel.Message, 10), +// Quitch: make(chan struct{}), +// } + +// signalChannel := &channel.SignalChannel{} + +// client := &client.HttpClient{ +// BaseUrl: baseurl, +// Client: &http.Client{ +// Timeout: time.Duration(time.Second * time.Duration(timeout)), +// Transport: &http.Transport{ +// MaxIdleConns: 10, +// MaxIdleConnsPerHost: 10, +// }, +// }, +// } + +// CodeProcessor := &processor.CodeProcessor{ +// Client: client, +// CodeChannel: codeChannel, +// SignalChannel: signalChannel, +// RegisterNumber: limit, +// } + +// CodeProcessor.Start() From f87debb98b2d2b11ac8c184301ac634bbd944809 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 13:57:33 +0100 Subject: [PATCH 22/83] Less code, easier to read and stable --- channel/code_channel.go | 31 -------- client/client.go | 10 --- client/http_client.go | 22 ------ codegenerator/code_generator.go | 29 +++++++ main.go | 129 +++++-------------------------- processor/code_processor.go | 112 ++++++++++++--------------- processor/code_processor_test.go | 27 +++---- register/code_register.go | 32 -------- 8 files changed, 106 insertions(+), 286 deletions(-) delete mode 100644 channel/code_channel.go delete mode 100644 client/client.go delete mode 100644 client/http_client.go create mode 100644 codegenerator/code_generator.go delete mode 100644 register/code_register.go diff --git a/channel/code_channel.go b/channel/code_channel.go deleted file mode 100644 index f3d2e3f..0000000 --- a/channel/code_channel.go +++ /dev/null @@ -1,31 +0,0 @@ -package channel - -import ( - "fmt" - "time" -) - -type Message struct { - Code string - Status string -} - -type CodeChannel struct { - Msgch chan Message - Quitch chan struct{} -} - -func (cc *CodeChannel) StartAndListen() { -listening: - for { - select { - case msg := <-cc.Msgch: - fmt.Printf("code: %s with status: %s\n", msg.Code, msg.Status) - case <-cc.Quitch: - fmt.Print("shutting down...\n") - time.Sleep(1000) - // os.Exit(4) - break listening - } - } -} diff --git a/client/client.go b/client/client.go deleted file mode 100644 index 05e0656..0000000 --- a/client/client.go +++ /dev/null @@ -1,10 +0,0 @@ -package client - -import ( - "io" - "net/http" -) - -type Client interface { - Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) -} diff --git a/client/http_client.go b/client/http_client.go deleted file mode 100644 index b054c13..0000000 --- a/client/http_client.go +++ /dev/null @@ -1,22 +0,0 @@ -package client - -import ( - "fmt" - "io" - "net/http" -) - -type HttpClient struct { - BaseUrl string - Client Client -} - -func (h *HttpClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { - fullUrl := fmt.Sprintf("%s/%s", h.BaseUrl, url) - resp, err = h.Client.Post(fullUrl, contentType, body) - if err != nil { - return nil, err - } - - return resp, nil -} diff --git a/codegenerator/code_generator.go b/codegenerator/code_generator.go new file mode 100644 index 0000000..e62a601 --- /dev/null +++ b/codegenerator/code_generator.go @@ -0,0 +1,29 @@ +package codegenerator + +import ( + "crypto/rand" + "math/big" +) + +const allowedChars = "ABCDEF0123456789" + +func Generate() (string, error) { + hexStr, err := generateHexString(16) + if err != nil { + return "", err + } + return hexStr[len(hexStr)-5:], nil +} + +func generateHexString(length int) (string, error) { + max := big.NewInt(int64(len(allowedChars))) + b := make([]byte, length) + for i := range b { + n, err := rand.Int(rand.Reader, max) + if err != nil { + return "", err + } + b[i] = allowedChars[n.Int64()] + } + return string(b), nil +} diff --git a/main.go b/main.go index ba5c1c5..8662233 100644 --- a/main.go +++ b/main.go @@ -1,123 +1,32 @@ package main import ( - "bytes" - "crypto/rand" - "encoding/json" - "fmt" - "log" - "math/big" - "net/http" - "sync" - "sync/atomic" - "time" + "os" + + "github.com/NickGowdy/deveui-cli/channel" + "github.com/NickGowdy/deveui-cli/processor" + "github.com/joho/godotenv" ) -const MAX_CONCURRENT_JOBS = 10 -const allowedChars = "ABCDEF0123456789" +const ( + MAX_CONCURRENT_JOBS = 10 + CODE_REGISTRATION_LIMIT = 10 + TIMEOUT = /* Seconds */ 30000 +) func main() { - waitChan := make(chan struct{}, MAX_CONCURRENT_JOBS) - var count int32 - var wg sync.WaitGroup - - for count < 100 { - wg.Add(1) - waitChan <- struct{}{} - - go func(ops int32) { - saved := job() - if saved { - atomic.AddInt32(&count, 1) - } - - <-waitChan - wg.Done() - }(count) - } - - close(waitChan) -} - -func job() bool { - hexStr, err := generateHexString(16) - if err != nil { - log.Print(err) - } - code := hexStr[len(hexStr)-5:] - client := http.Client{Timeout: time.Second * 30} - - b := new(bytes.Buffer) - reqBody := map[string]string{"Deveui": code} - - err = json.NewEncoder(b).Encode(&reqBody) - if err != nil { - log.Print(err) - } + godotenv.Load(".env") + baseurl := os.Getenv("BASE_URL") - resp, err := client.Post("http://europe-west1-machinemax-dev-d524.cloudfunctions.net/sensor-onboarding-sample", "application/json", b) + signalChannel := &channel.SignalChannel{} - if err != nil { - log.Print(err) + CodeProcessor := &processor.CodeProcessor{ + MaxConcurrentJobs: MAX_CONCURRENT_JOBS, + BaseUrl: baseurl, + CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, } - defer resp.Body.Close() + go signalChannel.StartAndListen() - fmt.Printf("%s\n", resp.Status) - - return resp.StatusCode == http.StatusOK -} - -func generateHexString(length int) (string, error) { - max := big.NewInt(int64(len(allowedChars))) - b := make([]byte, length) - for i := range b { - n, err := rand.Int(rand.Reader, max) - if err != nil { - return "", err - } - b[i] = allowedChars[n.Int64()] - } - return string(b), nil + CodeProcessor.Start() } - -// godotenv.Load(".env") - -// baseurl := os.Getenv("BASE_URL") - -// timeout, err := strconv.Atoi(os.Getenv("TIMEOUT")) -// if err != nil { -// log.Fatal(err) -// } - -// limit, err := strconv.Atoi(os.Getenv("CODE_REGISTRATION_LIMIT")) -// if err != nil { -// log.Fatal(err) -// } - -// codeChannel := &channel.CodeChannel{ -// Msgch: make(chan channel.Message, 10), -// Quitch: make(chan struct{}), -// } - -// signalChannel := &channel.SignalChannel{} - -// client := &client.HttpClient{ -// BaseUrl: baseurl, -// Client: &http.Client{ -// Timeout: time.Duration(time.Second * time.Duration(timeout)), -// Transport: &http.Transport{ -// MaxIdleConns: 10, -// MaxIdleConnsPerHost: 10, -// }, -// }, -// } - -// CodeProcessor := &processor.CodeProcessor{ -// Client: client, -// CodeChannel: codeChannel, -// SignalChannel: signalChannel, -// RegisterNumber: limit, -// } - -// CodeProcessor.Start() diff --git a/processor/code_processor.go b/processor/code_processor.go index 3ec2b99..ad20dcb 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -1,86 +1,70 @@ package processor import ( - "crypto/rand" + "bytes" + "encoding/json" + "fmt" "log" - "math/big" - "sync" + "net/http" + "sync/atomic" + "time" - "github.com/NickGowdy/deveui-cli/channel" - "github.com/NickGowdy/deveui-cli/client" - "github.com/NickGowdy/deveui-cli/register" + "github.com/NickGowdy/deveui-cli/codegenerator" ) -const allowedChars = "ABCDEF0123456789" - type CodeProcessor struct { - Client client.Client - CodeChannel *channel.CodeChannel - SignalChannel *channel.SignalChannel - RegisterNumber int + CodeRegistrationLimit int32 + MaxConcurrentJobs int + BaseUrl string } func (cp *CodeProcessor) Start() { - go cp.CodeChannel.StartAndListen() - go cp.SignalChannel.StartAndListen() - process(cp) -} + waitChan := make(chan struct{}, cp.MaxConcurrentJobs) + var count int32 -func process(cp *CodeProcessor) { - var i int - var lock sync.Mutex - var wg = &sync.WaitGroup{} - for i < cp.RegisterNumber { - - hexStr, err := generateHexString(16) - if err != nil { - log.Print(err) - } - code := hexStr[len(hexStr)-5:] - codeRegister := ®ister.CodeRegister{ - HttpClient: cp.Client, - Code: code, - } - wg.Add(1) - go func(code string) { - - resp, err := codeRegister.RegisterCode() - if err != nil { - log.Print(err) + for count < cp.CodeRegistrationLimit { + waitChan <- struct{}{} + go func(ops int32) { + saved := job() + if saved { + atomic.AddInt32(&count, 1) } - if resp != nil { - defer resp.Body.Close() + <-waitChan - msg := channel.Message{ - Code: code, - Status: resp.Status, - } + }(count) + } - cp.CodeChannel.Msgch <- msg - if resp.StatusCode == 200 { - lock.Lock() - defer lock.Unlock() - i++ - } - } + close(waitChan) +} + +func job() bool { + client := http.Client{Timeout: time.Second * 30} + code, err := codegenerator.Generate() - }(code) - wg.Done() + if err != nil { + log.Print(err) + return false } - close(cp.CodeChannel.Quitch) -} -func generateHexString(length int) (string, error) { - max := big.NewInt(int64(len(allowedChars))) - b := make([]byte, length) - for i := range b { - n, err := rand.Int(rand.Reader, max) - if err != nil { - return "", err - } - b[i] = allowedChars[n.Int64()] + b := new(bytes.Buffer) + reqBody := map[string]string{"Deveui": code} + + err = json.NewEncoder(b).Encode(&reqBody) + if err != nil { + log.Print(err) + } + + resp, err := client.Post("http://europe-west1-machinemax-dev-d524.cloudfunctions.net/sensor-onboarding-sample", "application/json", b) + + if err != nil { + log.Print(err) } - return string(b), nil + + defer resp.Body.Close() + + fmt.Printf("%s\n", resp.Status) + + return resp.StatusCode == http.StatusOK } diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index d6bbed4..e2b6fd6 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -8,8 +8,6 @@ import ( "net/http" "os" "testing" - - "github.com/NickGowdy/deveui-cli/channel" ) type MockClient struct { @@ -17,24 +15,19 @@ type MockClient struct { } func TestCanProcessCodes(t *testing.T) { - codeChannel := &channel.CodeChannel{ - Msgch: make(chan channel.Message, 10), - Quitch: make(chan struct{}), - } + // codeChannel := &channel.CodeChannel{ + // Msgch: make(chan channel.Message, 10), + // Quitch: make(chan struct{}), + // } - signalChannel := &channel.SignalChannel{} - - client := &MockClient{ - DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { - return &http.Response{}, nil - }, - } + // client := &MockClient{ + // DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + // return &http.Response{}, nil + // }, + // } CodeProcessor := &CodeProcessor{ - Client: client, - CodeChannel: codeChannel, - SignalChannel: signalChannel, - RegisterNumber: 10, + MaxConcurrentJobs: 10, } reader := bufio.NewReader(os.Stdin) diff --git a/register/code_register.go b/register/code_register.go deleted file mode 100644 index e88d2df..0000000 --- a/register/code_register.go +++ /dev/null @@ -1,32 +0,0 @@ -package register - -import ( - "bytes" - "encoding/json" - "log" - "net/http" - - "github.com/NickGowdy/deveui-cli/client" -) - -type CodeRegister struct { - HttpClient client.Client - Code string -} - -func (cr CodeRegister) RegisterCode() (*http.Response, error) { - b := new(bytes.Buffer) - reqBody := map[string]string{"Deveui": cr.Code} - - err := json.NewEncoder(b).Encode(&reqBody) - if err != nil { - log.Print(err) - } - - resp, err := cr.HttpClient.Post("sensor-onboarding-sample", "application/json", b) - if err != nil { - log.Print(err) - } - - return resp, nil -} From b1306818cad67ef50441fe4058c1d01490ab5b31 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 13:59:07 +0100 Subject: [PATCH 23/83] Reuse client --- processor/code_processor.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/processor/code_processor.go b/processor/code_processor.go index ad20dcb..754bbe1 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -19,14 +19,14 @@ type CodeProcessor struct { } func (cp *CodeProcessor) Start() { - waitChan := make(chan struct{}, cp.MaxConcurrentJobs) + client := http.Client{Timeout: time.Second * 30} var count int32 for count < cp.CodeRegistrationLimit { waitChan <- struct{}{} go func(ops int32) { - saved := job() + saved := job(&client) if saved { atomic.AddInt32(&count, 1) } @@ -39,8 +39,8 @@ func (cp *CodeProcessor) Start() { close(waitChan) } -func job() bool { - client := http.Client{Timeout: time.Second * 30} +func job(client *http.Client) bool { + code, err := codegenerator.Generate() if err != nil { From 799b58c9881d6d33f6b391705e62fc6a843b97c3 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 14:29:22 +0100 Subject: [PATCH 24/83] code generator unit tests --- codegenerator/code_generator_test.go | 83 ++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 codegenerator/code_generator_test.go diff --git a/codegenerator/code_generator_test.go b/codegenerator/code_generator_test.go new file mode 100644 index 0000000..bf9391a --- /dev/null +++ b/codegenerator/code_generator_test.go @@ -0,0 +1,83 @@ +package codegenerator + +import ( + "testing" +) + +func TestCanGenerateValidCode(t *testing.T) { + allowedChars := []string{"A", "B", "C", "D", "E", "F", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} + code, err := Generate() + + if err != nil { + t.Errorf("error should be nil, but is %s", err.Error()) + } + + if len(code) != 5 { + t.Errorf("code should be 5 characters long, but is %d", len(code)) + } + + hasChar := false + for _, char := range allowedChars { + if char == string(code[0]) { + hasChar = true + } else if hasChar { + break + } + } + + if hasChar == false { + t.Errorf("first char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[0])) + } + + hasChar = false + for _, char := range allowedChars { + if char == string(code[1]) { + hasChar = true + } else if hasChar { + break + } + } + + if hasChar == false { + t.Errorf("second char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[1])) + } + + hasChar = false + for _, char := range allowedChars { + if char == string(code[2]) { + hasChar = true + } else if hasChar { + break + } + } + + if hasChar == false { + t.Errorf("third char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[2])) + } + + hasChar = false + for _, char := range allowedChars { + if char == string(code[3]) { + hasChar = true + } else if hasChar { + break + } + } + + if hasChar == false { + t.Errorf("fourth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[3])) + } + + hasChar = false + for _, char := range allowedChars { + if char == string(code[4]) { + hasChar = true + } else if hasChar { + break + } + } + + if hasChar == false { + t.Errorf("fifth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[4])) + } +} From adac66646bf360c8e9413c474d3c0a471f408623 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 15:22:57 +0100 Subject: [PATCH 25/83] use interface --- client/client.go | 10 ++++++++++ client/lorawan_client.go | 23 +++++++++++++++++++++++ main.go | 9 +++++++++ processor/code_processor.go | 11 ++++++----- processor/code_processor_test.go | 20 +++++++++----------- 5 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 client/client.go create mode 100644 client/lorawan_client.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..05e0656 --- /dev/null +++ b/client/client.go @@ -0,0 +1,10 @@ +package client + +import ( + "io" + "net/http" +) + +type Client interface { + Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) +} diff --git a/client/lorawan_client.go b/client/lorawan_client.go new file mode 100644 index 0000000..2ed5893 --- /dev/null +++ b/client/lorawan_client.go @@ -0,0 +1,23 @@ +package client + +import ( + "fmt" + "io" + "net/http" +) + +type LoraWanClient struct { + Client Client +} + +const endpoint = "/sensor-onboarding-sample" + +func (h *LoraWanClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + fullUrl := fmt.Sprintf("%s/%s", url, endpoint) + resp, err = h.Client.Post(fullUrl, contentType, body) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/main.go b/main.go index 8662233..601aba2 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,12 @@ package main import ( + "net/http" "os" + "time" "github.com/NickGowdy/deveui-cli/channel" + "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/processor" "github.com/joho/godotenv" ) @@ -19,11 +22,17 @@ func main() { baseurl := os.Getenv("BASE_URL") signalChannel := &channel.SignalChannel{} + httpClient := &http.Client{ + Timeout: time.Second * TIMEOUT, + } CodeProcessor := &processor.CodeProcessor{ MaxConcurrentJobs: MAX_CONCURRENT_JOBS, BaseUrl: baseurl, CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, + Client: &client.LoraWanClient{ + Client: httpClient, + }, } go signalChannel.StartAndListen() diff --git a/processor/code_processor.go b/processor/code_processor.go index 754bbe1..b81afcf 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -7,8 +7,8 @@ import ( "log" "net/http" "sync/atomic" - "time" + "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/codegenerator" ) @@ -16,17 +16,18 @@ type CodeProcessor struct { CodeRegistrationLimit int32 MaxConcurrentJobs int BaseUrl string + Client client.Client } func (cp *CodeProcessor) Start() { waitChan := make(chan struct{}, cp.MaxConcurrentJobs) - client := http.Client{Timeout: time.Second * 30} + // client := http.Client{Timeout: time.Second * 30} var count int32 for count < cp.CodeRegistrationLimit { waitChan <- struct{}{} go func(ops int32) { - saved := job(&client) + saved := job(cp.Client, cp.BaseUrl) if saved { atomic.AddInt32(&count, 1) } @@ -39,7 +40,7 @@ func (cp *CodeProcessor) Start() { close(waitChan) } -func job(client *http.Client) bool { +func job(client client.Client, url string) bool { code, err := codegenerator.Generate() @@ -56,7 +57,7 @@ func job(client *http.Client) bool { log.Print(err) } - resp, err := client.Post("http://europe-west1-machinemax-dev-d524.cloudfunctions.net/sensor-onboarding-sample", "application/json", b) + resp, err := client.Post(url, "application/json", b) if err != nil { log.Print(err) diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index e2b6fd6..6aa407c 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -15,19 +15,17 @@ type MockClient struct { } func TestCanProcessCodes(t *testing.T) { - // codeChannel := &channel.CodeChannel{ - // Msgch: make(chan channel.Message, 10), - // Quitch: make(chan struct{}), - // } - - // client := &MockClient{ - // DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { - // return &http.Response{}, nil - // }, - // } + client := &MockClient{ + DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + return &http.Response{}, nil + }, + } CodeProcessor := &CodeProcessor{ - MaxConcurrentJobs: 10, + CodeRegistrationLimit: 0, + MaxConcurrentJobs: 10, + BaseUrl: "http://www.mock-url.com", + Client: client, } reader := bufio.NewReader(os.Stdin) From 07afe6ff7f6a81bb1e37bf46474f9076583b4f74 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 15:25:48 +0100 Subject: [PATCH 26/83] comments --- main.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 601aba2..ac1846d 100644 --- a/main.go +++ b/main.go @@ -21,21 +21,25 @@ func main() { godotenv.Load(".env") baseurl := os.Getenv("BASE_URL") + // setup channel for listening to SIG cmds signalChannel := &channel.SignalChannel{} + + // setup clients for requests httpClient := &http.Client{ Timeout: time.Second * TIMEOUT, } + loraWanClient := &client.LoraWanClient{ + Client: httpClient, + } + // setup processor to do work CodeProcessor := &processor.CodeProcessor{ MaxConcurrentJobs: MAX_CONCURRENT_JOBS, BaseUrl: baseurl, CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, - Client: &client.LoraWanClient{ - Client: httpClient, - }, + Client: loraWanClient, } go signalChannel.StartAndListen() - CodeProcessor.Start() } From 6876d48146756dacc0c4b94bbfded5b5b48f0249 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 15:41:58 +0100 Subject: [PATCH 27/83] Return registered devices --- main.go | 15 +++++++++++---- processor/code_processor.go | 20 +++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index ac1846d..62a97b3 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net/http" "os" "time" @@ -12,8 +13,8 @@ import ( ) const ( - MAX_CONCURRENT_JOBS = 10 - CODE_REGISTRATION_LIMIT = 10 + MAX_CONCURRENT_JOBS = /* Buffer limit for channel */ 10 + CODE_REGISTRATION_LIMIT = /* Maximum number of devices that will be registered */ 100 TIMEOUT = /* Seconds */ 30000 ) @@ -24,7 +25,7 @@ func main() { // setup channel for listening to SIG cmds signalChannel := &channel.SignalChannel{} - // setup clients for requests + // setup client for requests httpClient := &http.Client{ Timeout: time.Second * TIMEOUT, } @@ -41,5 +42,11 @@ func main() { } go signalChannel.StartAndListen() - CodeProcessor.Start() + registeredDevices := CodeProcessor.Start() + + for _, d := range *registeredDevices { + for k, v := range d { + fmt.Println(k, "value is", v) + } + } } diff --git a/processor/code_processor.go b/processor/code_processor.go index b81afcf..5fa2e22 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -17,19 +17,20 @@ type CodeProcessor struct { MaxConcurrentJobs int BaseUrl string Client client.Client + RegisteredDevices []map[int]string } -func (cp *CodeProcessor) Start() { +func (cp *CodeProcessor) Start() *[]map[int]string { waitChan := make(chan struct{}, cp.MaxConcurrentJobs) - // client := http.Client{Timeout: time.Second * 30} var count int32 for count < cp.CodeRegistrationLimit { waitChan <- struct{}{} - go func(ops int32) { - saved := job(cp.Client, cp.BaseUrl) + go func(innerCount int32) { + saved, code := process(cp.Client, cp.BaseUrl) if saved { atomic.AddInt32(&count, 1) + cp.RegisteredDevices = append(cp.RegisteredDevices, map[int]string{int(count): code}) } <-waitChan @@ -38,15 +39,16 @@ func (cp *CodeProcessor) Start() { } close(waitChan) + return &cp.RegisteredDevices } -func job(client client.Client, url string) bool { +func process(client client.Client, url string) (bool, string) { code, err := codegenerator.Generate() if err != nil { log.Print(err) - return false + return false, "" } b := new(bytes.Buffer) @@ -67,5 +69,9 @@ func job(client client.Client, url string) bool { fmt.Printf("%s\n", resp.Status) - return resp.StatusCode == http.StatusOK + if resp.StatusCode == http.StatusOK { + return true, code + } else { + return false, "" + } } From 4c6d0360f42fcf222d7de688bd6a801e6522a696 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 16:01:15 +0100 Subject: [PATCH 28/83] More tests --- client/lorawan_client_test.go | 48 ++++++++++++++++++++++++++++++++ main.go | 2 +- processor/code_processor.go | 6 ++-- processor/code_processor_test.go | 14 ++++------ 4 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 client/lorawan_client_test.go diff --git a/client/lorawan_client_test.go b/client/lorawan_client_test.go new file mode 100644 index 0000000..e7b8cd5 --- /dev/null +++ b/client/lorawan_client_test.go @@ -0,0 +1,48 @@ +package client + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "testing" +) + +type MockClient struct { + DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) +} + +func TestLorawanClientHappyPath(t *testing.T) { + mockClient := &MockClient{ + DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + return &http.Response{}, nil + }, + } + + loraWanClient := LoraWanClient{ + Client: mockClient, + } + + b := new(bytes.Buffer) + reqBody := map[string]string{"Deveui": "Abcde"} + + _ = json.NewEncoder(b).Encode(&reqBody) + + resp, err := loraWanClient.Post("mock-url", "application/json", b) + + if err != nil { + t.Errorf("err should be nil but is: %s", err.Error()) + } + + if resp.StatusCode != 200 { + t.Errorf("resp should be nil but is: %d", resp.StatusCode) + } +} + +func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(nil)), + Status: "200 OK"}, + nil +} diff --git a/main.go b/main.go index 62a97b3..c92b0e0 100644 --- a/main.go +++ b/main.go @@ -42,7 +42,7 @@ func main() { } go signalChannel.StartAndListen() - registeredDevices := CodeProcessor.Start() + registeredDevices := CodeProcessor.Process() for _, d := range *registeredDevices { for k, v := range d { diff --git a/processor/code_processor.go b/processor/code_processor.go index 5fa2e22..378f987 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -20,14 +20,14 @@ type CodeProcessor struct { RegisteredDevices []map[int]string } -func (cp *CodeProcessor) Start() *[]map[int]string { +func (cp *CodeProcessor) Process() *[]map[int]string { waitChan := make(chan struct{}, cp.MaxConcurrentJobs) var count int32 for count < cp.CodeRegistrationLimit { waitChan <- struct{}{} go func(innerCount int32) { - saved, code := process(cp.Client, cp.BaseUrl) + saved, code := registerDevice(cp.Client, cp.BaseUrl) if saved { atomic.AddInt32(&count, 1) cp.RegisteredDevices = append(cp.RegisteredDevices, map[int]string{int(count): code}) @@ -42,7 +42,7 @@ func (cp *CodeProcessor) Start() *[]map[int]string { return &cp.RegisteredDevices } -func process(client client.Client, url string) (bool, string) { +func registerDevice(client client.Client, url string) (bool, string) { code, err := codegenerator.Generate() diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 6aa407c..00d0f46 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -1,12 +1,9 @@ package processor import ( - "bufio" "bytes" - "fmt" "io" "net/http" - "os" "testing" ) @@ -22,17 +19,18 @@ func TestCanProcessCodes(t *testing.T) { } CodeProcessor := &CodeProcessor{ - CodeRegistrationLimit: 0, + CodeRegistrationLimit: 10, MaxConcurrentJobs: 10, BaseUrl: "http://www.mock-url.com", Client: client, } - reader := bufio.NewReader(os.Stdin) - CodeProcessor.Start() + registeredDevices := CodeProcessor.Process() + + if len(*registeredDevices) != 10 { + t.Errorf("expecting 10 registered devices, but have: %d", len(*registeredDevices)) + } - text, _ := reader.ReadString('\n') - fmt.Println(text) } func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { From 4891795f3d25c7dcf24d7da799d00d55d23aa7ec Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 16:04:22 +0100 Subject: [PATCH 29/83] Formatting of string --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index c92b0e0..7940b3f 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,7 @@ func main() { for _, d := range *registeredDevices { for k, v := range d { - fmt.Println(k, "value is", v) + fmt.Printf("device: %d has id: %s\n", k, v) } } } From c55dfe5ccc7af8a4d7c3a8af103c19c336aaf7cb Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 16:10:28 +0100 Subject: [PATCH 30/83] dockerfile --- dockerfile | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 dockerfile diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..e12dc57 --- /dev/null +++ b/dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.19-alpine + +RUN apk add --no-cache git + +# Set the Current Working Directory inside the container +WORKDIR /app/deveui-cli + +# We want to populate the module cache based on the go.{mod,sum} files. +COPY go.mod . + +RUN go mod download + +COPY . . + +# Build the Go app +RUN go build -o ./deveui-cli . + +# This container exposes port 8080 to the outside world +EXPOSE 8081 + +# Run the binary program produced by `go install` +ENTRYPOINT ["./deveui-cli"] \ No newline at end of file From 301a8c31060adbb40e032907a535636d237d1900 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 16:10:43 +0100 Subject: [PATCH 31/83] more readme --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d0bde06..9ea9922 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ -# deveui-cli +## deveui-cli -From the root run `touch .env` and add these vars: +

This is a command line application written in Golang for generation of unique 16-character (hex) identifiers called DevEUI.

+ +

These DevEUIs are used for each MachineMax senor.

+ + +## How to run this client library + + + +To run this code locally, run `touch .env` and add these vars: ``` BASE_URL=http://europe-west1-machinemax-dev-d524.cloudfunctions.net @@ -10,4 +19,9 @@ CODE_REGISTRATION_LIMIT=100 Then run `go run main.go` to register devices. -More content to be added later. \ No newline at end of file + +Alternatively, this code can also be run via [Docker](https://www.docker.com/). To build the docker image use: `docker build -t deveui-cli . ` + +To run the docker image: `docker run deveui-cli` + +Finally, to run the tests use: `go test ./...` and to check code coverage: `go test -coverprofile=coverage.out ./... ; go tool cover -html=coverage.out` From 066e9616481dc672d98aecb130fc6539b6a0d2ea Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 16:39:57 +0100 Subject: [PATCH 32/83] dont need port number here --- dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/dockerfile b/dockerfile index e12dc57..467cc91 100644 --- a/dockerfile +++ b/dockerfile @@ -15,8 +15,6 @@ COPY . . # Build the Go app RUN go build -o ./deveui-cli . -# This container exposes port 8080 to the outside world -EXPOSE 8081 # Run the binary program produced by `go install` ENTRYPOINT ["./deveui-cli"] \ No newline at end of file From 8dd927a04aba8dc43df45433df9344df56aff5bc Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 16:40:15 +0100 Subject: [PATCH 33/83] fatal if cant contact server --- processor/code_processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/code_processor.go b/processor/code_processor.go index 378f987..9b803dd 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -62,7 +62,7 @@ func registerDevice(client client.Client, url string) (bool, string) { resp, err := client.Post(url, "application/json", b) if err != nil { - log.Print(err) + log.Fatal(err) } defer resp.Body.Close() From da047dd6570b6222bced843ac3429cba5b40bf71 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 16:40:22 +0100 Subject: [PATCH 34/83] more instructions --- README.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9ea9922..2794dc1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ ## deveui-cli -

This is a command line application written in Golang for generation of unique 16-character (hex) identifiers called DevEUI.

- -

These DevEUIs are used for each MachineMax senor.

+

This is a command line application written in Golang for generation of unique 16-character (hex) identifiers called DevEUI which are used for each MachineMax senor

## How to run this client library - - -To run this code locally, run `touch .env` and add these vars: +To run this code, first run `touch .env` and add these vars: ``` BASE_URL=http://europe-west1-machinemax-dev-d524.cloudfunctions.net @@ -17,11 +13,11 @@ TIMEOUT=30000 CODE_REGISTRATION_LIMIT=100 ``` -Then run `go run main.go` to register devices. +Then to run locally, use: `go run main.go`. -Alternatively, this code can also be run via [Docker](https://www.docker.com/). To build the docker image use: `docker build -t deveui-cli . ` +Alternatively, this code can also be run via [Docker](https://www.docker.com/). To build the docker image use: `docker build -t deveui-cli . --build-arg BASE_URL=${BASE_URL} --build-arg TIMEOUT=${TIMEOUT} --build-arg CODE_REGISTRATION_LIMIT=${CODE_REGISTRATION_LIMIT}` (After touch .env and copying env variables from above). To run the docker image: `docker run deveui-cli` -Finally, to run the tests use: `go test ./...` and to check code coverage: `go test -coverprofile=coverage.out ./... ; go tool cover -html=coverage.out` +To run the tests use: `go test ./...` and to check code coverage: `go test -coverprofile=coverage.out ./... ; go tool cover -html=coverage.out` From 4e31de468042f062302ee808aaf182765290eb97 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 16:51:57 +0100 Subject: [PATCH 35/83] show hex with id --- codegenerator/code_generator.go | 11 ++++------- codegenerator/code_generator_test.go | 3 ++- main.go | 8 +++----- processor/code_processor.go | 29 ++++++++++++++++++---------- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/codegenerator/code_generator.go b/codegenerator/code_generator.go index e62a601..3462f51 100644 --- a/codegenerator/code_generator.go +++ b/codegenerator/code_generator.go @@ -7,15 +7,12 @@ import ( const allowedChars = "ABCDEF0123456789" -func Generate() (string, error) { - hexStr, err := generateHexString(16) - if err != nil { - return "", err - } - return hexStr[len(hexStr)-5:], nil +func Generate(hex string) (string, error) { + + return hex[len(hex)-5:], nil } -func generateHexString(length int) (string, error) { +func GenerateHexString(length int) (string, error) { max := big.NewInt(int64(len(allowedChars))) b := make([]byte, length) for i := range b { diff --git a/codegenerator/code_generator_test.go b/codegenerator/code_generator_test.go index bf9391a..232411a 100644 --- a/codegenerator/code_generator_test.go +++ b/codegenerator/code_generator_test.go @@ -6,7 +6,8 @@ import ( func TestCanGenerateValidCode(t *testing.T) { allowedChars := []string{"A", "B", "C", "D", "E", "F", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} - code, err := Generate() + hex, _ := GenerateHexString(16) + code, err := Generate(hex) if err != nil { t.Errorf("error should be nil, but is %s", err.Error()) diff --git a/main.go b/main.go index 7940b3f..22d20bd 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,7 @@ import ( const ( MAX_CONCURRENT_JOBS = /* Buffer limit for channel */ 10 - CODE_REGISTRATION_LIMIT = /* Maximum number of devices that will be registered */ 100 + CODE_REGISTRATION_LIMIT = /* Maximum number of devices that will be registered */ 10 TIMEOUT = /* Seconds */ 30000 ) @@ -44,9 +44,7 @@ func main() { go signalChannel.StartAndListen() registeredDevices := CodeProcessor.Process() - for _, d := range *registeredDevices { - for k, v := range d { - fmt.Printf("device: %d has id: %s\n", k, v) - } + for i, d := range *registeredDevices { + fmt.Printf("device: %d has identifier: %s and code: %s\n", i+1, d.Identifier, d.Code) } } diff --git a/processor/code_processor.go b/processor/code_processor.go index 9b803dd..fc8883f 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -17,20 +17,25 @@ type CodeProcessor struct { MaxConcurrentJobs int BaseUrl string Client client.Client - RegisteredDevices []map[int]string + RegisteredDevices []RegisterDevice } -func (cp *CodeProcessor) Process() *[]map[int]string { +type RegisterDevice struct { + Identifier string + Code string +} + +func (cp *CodeProcessor) Process() *[]RegisterDevice { waitChan := make(chan struct{}, cp.MaxConcurrentJobs) var count int32 for count < cp.CodeRegistrationLimit { waitChan <- struct{}{} go func(innerCount int32) { - saved, code := registerDevice(cp.Client, cp.BaseUrl) + saved, registeredDevice := registerDevice(cp.Client, cp.BaseUrl) if saved { atomic.AddInt32(&count, 1) - cp.RegisteredDevices = append(cp.RegisteredDevices, map[int]string{int(count): code}) + cp.RegisteredDevices = append(cp.RegisteredDevices, registeredDevice) } <-waitChan @@ -42,13 +47,17 @@ func (cp *CodeProcessor) Process() *[]map[int]string { return &cp.RegisteredDevices } -func registerDevice(client client.Client, url string) (bool, string) { - - code, err := codegenerator.Generate() +func registerDevice(client client.Client, url string) (bool, RegisterDevice) { + hex, err := codegenerator.GenerateHexString(16) + if err != nil { + log.Print(err) + return false, RegisterDevice{} + } + code, err := codegenerator.Generate(hex) if err != nil { log.Print(err) - return false, "" + return false, RegisterDevice{} } b := new(bytes.Buffer) @@ -70,8 +79,8 @@ func registerDevice(client client.Client, url string) (bool, string) { fmt.Printf("%s\n", resp.Status) if resp.StatusCode == http.StatusOK { - return true, code + return true, RegisterDevice{Code: code, Identifier: hex} } else { - return false, "" + return false, RegisterDevice{} } } From 15eb611e8ee28731f980342c241244f1063f1792 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 17:42:52 +0100 Subject: [PATCH 36/83] Trying to fix race condition --- processor/code_processor.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/processor/code_processor.go b/processor/code_processor.go index fc8883f..9fd3681 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -6,14 +6,14 @@ import ( "fmt" "log" "net/http" - "sync/atomic" + "sync" "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/codegenerator" ) type CodeProcessor struct { - CodeRegistrationLimit int32 + CodeRegistrationLimit int MaxConcurrentJobs int BaseUrl string Client client.Client @@ -27,20 +27,26 @@ type RegisterDevice struct { func (cp *CodeProcessor) Process() *[]RegisterDevice { waitChan := make(chan struct{}, cp.MaxConcurrentJobs) - var count int32 + var count int + m := sync.Mutex{} + wg := sync.WaitGroup{} for count < cp.CodeRegistrationLimit { waitChan <- struct{}{} - go func(innerCount int32) { + wg.Add(1) + go func(innerCount int) { saved, registeredDevice := registerDevice(cp.Client, cp.BaseUrl) if saved { - atomic.AddInt32(&count, 1) + m.Lock() + count++ cp.RegisteredDevices = append(cp.RegisteredDevices, registeredDevice) + defer m.Unlock() } <-waitChan }(count) + wg.Done() } close(waitChan) From c93a6dfadb184dc466810427e6d82e9ebe8c218c Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 20 Apr 2023 17:43:14 +0100 Subject: [PATCH 37/83] more changes --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2794dc1..333c9c6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -## deveui-cli +# MachineMax deveui-cli +

This is a command line application written in Golang for generation of unique 16-character (hex) identifiers called DevEUI which are used for each MachineMax senor

From ec319bdd4569cc60d23a4ebe56d592e5fd94d49c Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 21 Apr 2023 07:22:05 +0100 Subject: [PATCH 38/83] Pass in context and use channels in main to fix race cond --- main.go | 53 +++++++++++++++++++++++++++++-------- processor/code_processor.go | 37 +++++++------------------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/main.go b/main.go index 22d20bd..587874c 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,15 @@ package main import ( + "context" "fmt" + "log" "net/http" "os" + "os/signal" + "syscall" "time" - "github.com/NickGowdy/deveui-cli/channel" "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/processor" "github.com/joho/godotenv" @@ -14,17 +17,17 @@ import ( const ( MAX_CONCURRENT_JOBS = /* Buffer limit for channel */ 10 - CODE_REGISTRATION_LIMIT = /* Maximum number of devices that will be registered */ 10 - TIMEOUT = /* Seconds */ 30000 + CODE_REGISTRATION_LIMIT = /* Maximum number of devices that will be registered */ 100 + TIMEOUT = /* Seconds */ 5 ) func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + godotenv.Load(".env") baseurl := os.Getenv("BASE_URL") - // setup channel for listening to SIG cmds - signalChannel := &channel.SignalChannel{} - // setup client for requests httpClient := &http.Client{ Timeout: time.Second * TIMEOUT, @@ -34,17 +37,45 @@ func main() { } // setup processor to do work - CodeProcessor := &processor.CodeProcessor{ + codeProcessor := &processor.CodeProcessor{ MaxConcurrentJobs: MAX_CONCURRENT_JOBS, BaseUrl: baseurl, CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, Client: loraWanClient, + RegisteredDevices: make(chan processor.RegisterDevice), } - go signalChannel.StartAndListen() - registeredDevices := CodeProcessor.Process() + work := make(chan struct{}, MAX_CONCURRENT_JOBS) + go func() { + for { + work <- struct{}{} + } + }() - for i, d := range *registeredDevices { - fmt.Printf("device: %d has identifier: %s and code: %s\n", i+1, d.Identifier, d.Code) + // Spawn workers + for j := 0; j < MAX_CONCURRENT_JOBS; j++ { + go codeProcessor.Worker(ctx, work) } + + n := 0 + for d := range codeProcessor.RegisteredDevices { + fmt.Printf("device: %d has identifier: %s and code: %s\n", n+1, d.Identifier, d.Code) + n += 1 + if n == CODE_REGISTRATION_LIMIT { + break + } + } + + // goroutine to listen for syscall.SIGTERM, syscall.SIGINT + go func() { + cancelChan := make(chan os.Signal, 1) + signal.Notify(cancelChan, syscall.SIGTERM, syscall.SIGINT) + go func() { + for { + time.Sleep(1000) + } + }() + sig := <-cancelChan + log.Printf("Caught signal %v", sig) + }() } diff --git a/processor/code_processor.go b/processor/code_processor.go index 9fd3681..e2afc58 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -2,11 +2,10 @@ package processor import ( "bytes" + "context" "encoding/json" - "fmt" "log" "net/http" - "sync" "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/codegenerator" @@ -17,7 +16,7 @@ type CodeProcessor struct { MaxConcurrentJobs int BaseUrl string Client client.Client - RegisteredDevices []RegisterDevice + RegisteredDevices chan RegisterDevice } type RegisterDevice struct { @@ -25,32 +24,18 @@ type RegisterDevice struct { Code string } -func (cp *CodeProcessor) Process() *[]RegisterDevice { - waitChan := make(chan struct{}, cp.MaxConcurrentJobs) - var count int - m := sync.Mutex{} - wg := sync.WaitGroup{} - - for count < cp.CodeRegistrationLimit { - waitChan <- struct{}{} - wg.Add(1) - go func(innerCount int) { +func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-work: saved, registeredDevice := registerDevice(cp.Client, cp.BaseUrl) if saved { - m.Lock() - count++ - cp.RegisteredDevices = append(cp.RegisteredDevices, registeredDevice) - defer m.Unlock() + cp.RegisteredDevices <- registeredDevice } - - <-waitChan - - }(count) - wg.Done() + } } - - close(waitChan) - return &cp.RegisteredDevices } func registerDevice(client client.Client, url string) (bool, RegisterDevice) { @@ -82,8 +67,6 @@ func registerDevice(client client.Client, url string) (bool, RegisterDevice) { defer resp.Body.Close() - fmt.Printf("%s\n", resp.Status) - if resp.StatusCode == http.StatusOK { return true, RegisterDevice{Code: code, Identifier: hex} } else { From a266a5f4276286028d80583febf99e016926b244 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 21 Apr 2023 07:22:12 +0100 Subject: [PATCH 39/83] delete file --- channel/signal_channel.go | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 channel/signal_channel.go diff --git a/channel/signal_channel.go b/channel/signal_channel.go deleted file mode 100644 index af115ed..0000000 --- a/channel/signal_channel.go +++ /dev/null @@ -1,23 +0,0 @@ -package channel - -import ( - "log" - "os" - "os/signal" - "syscall" - "time" -) - -type SignalChannel struct{} - -func (sc *SignalChannel) StartAndListen() { - cancelChan := make(chan os.Signal, 1) - signal.Notify(cancelChan, syscall.SIGTERM, syscall.SIGINT) - go func() { - for { - time.Sleep(1000) - } - }() - sig := <-cancelChan - log.Printf("Caught signal %v", sig) -} From 064be87226436929651502052bf3be181ce6d0da Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 21 Apr 2023 07:22:20 +0100 Subject: [PATCH 40/83] fix test errs --- processor/code_processor_test.go | 45 +++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 00d0f46..c8f453a 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -2,6 +2,8 @@ package processor import ( "bytes" + "context" + "fmt" "io" "net/http" "testing" @@ -11,6 +13,14 @@ type MockClient struct { DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) } +func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(nil)), + Status: "200 OK"}, + nil +} + func TestCanProcessCodes(t *testing.T) { client := &MockClient{ DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { @@ -18,25 +28,36 @@ func TestCanProcessCodes(t *testing.T) { }, } - CodeProcessor := &CodeProcessor{ + codeProcessor := &CodeProcessor{ CodeRegistrationLimit: 10, MaxConcurrentJobs: 10, BaseUrl: "http://www.mock-url.com", Client: client, + RegisteredDevices: make(chan RegisterDevice), } - registeredDevices := CodeProcessor.Process() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - if len(*registeredDevices) != 10 { - t.Errorf("expecting 10 registered devices, but have: %d", len(*registeredDevices)) - } + work := make(chan struct{}, 10) -} + go func() { + for { + work <- struct{}{} + } + }() -func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(nil)), - Status: "200 OK"}, - nil + // Spawn workers + for j := 0; j < 10; j++ { + go codeProcessor.Worker(ctx, work) + } + + n := 0 + for d := range codeProcessor.RegisteredDevices { + fmt.Printf("device: %d has identifier: %s and code: %s\n", n+1, d.Identifier, d.Code) + n += 1 + if n == 10 { + break + } + } } From 430d45c8790eb6e1ddbb263e3d7a91be48030772 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 21 Apr 2023 08:15:46 +0100 Subject: [PATCH 41/83] comments and tidy up --- client/client.go | 1 + client/lorawan_client.go | 4 +++- codegenerator/code_generator.go | 26 ++++++++++++++++------ codegenerator/code_generator_test.go | 4 ++-- main.go | 33 ++++++++++++++++++++-------- processor/code_processor.go | 12 ++++++++-- 6 files changed, 59 insertions(+), 21 deletions(-) diff --git a/client/client.go b/client/client.go index 05e0656..e4f580d 100644 --- a/client/client.go +++ b/client/client.go @@ -5,6 +5,7 @@ import ( "net/http" ) +// Generic client used to communicate to external services type Client interface { Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) } diff --git a/client/lorawan_client.go b/client/lorawan_client.go index 2ed5893..79c2f7b 100644 --- a/client/lorawan_client.go +++ b/client/lorawan_client.go @@ -6,12 +6,14 @@ import ( "net/http" ) +// Client used to communicate to LoRaWAN external system type LoraWanClient struct { Client Client } -const endpoint = "/sensor-onboarding-sample" +const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN +// Send data via POST (HTTP) request func (h *LoraWanClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { fullUrl := fmt.Sprintf("%s/%s", url, endpoint) resp, err = h.Client.Post(fullUrl, contentType, body) diff --git a/codegenerator/code_generator.go b/codegenerator/code_generator.go index 3462f51..a9eb7ed 100644 --- a/codegenerator/code_generator.go +++ b/codegenerator/code_generator.go @@ -5,22 +5,34 @@ import ( "math/big" ) -const allowedChars = "ABCDEF0123456789" - -func Generate(hex string) (string, error) { +const ( + ALLOWED_CHARS = "ABCDEF0123456789" // accepted chars used to make up DevEUI + DEV_EUI_LENGTH = 16 // valid DevEUI is string of length 16 +) +// Generate valid DevEUI code from DevEUI identifier. +// +// # Example +// +// 1CEB0080F074F750 will return 4F750 +func GenerateCode(hex string) (string, error) { return hex[len(hex)-5:], nil } -func GenerateHexString(length int) (string, error) { - max := big.NewInt(int64(len(allowedChars))) - b := make([]byte, length) +// Generate valid DevEUI identifier value. +// +// # Example +// +// 1CEB0080F074F750 +func GenerateHexString() (string, error) { + max := big.NewInt(int64(len(ALLOWED_CHARS))) + b := make([]byte, DEV_EUI_LENGTH) for i := range b { n, err := rand.Int(rand.Reader, max) if err != nil { return "", err } - b[i] = allowedChars[n.Int64()] + b[i] = ALLOWED_CHARS[n.Int64()] } return string(b), nil } diff --git a/codegenerator/code_generator_test.go b/codegenerator/code_generator_test.go index 232411a..7e9cb1e 100644 --- a/codegenerator/code_generator_test.go +++ b/codegenerator/code_generator_test.go @@ -6,8 +6,8 @@ import ( func TestCanGenerateValidCode(t *testing.T) { allowedChars := []string{"A", "B", "C", "D", "E", "F", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} - hex, _ := GenerateHexString(16) - code, err := Generate(hex) + hex, _ := GenerateHexString() + code, err := GenerateCode(hex) if err != nil { t.Errorf("error should be nil, but is %s", err.Error()) diff --git a/main.go b/main.go index 587874c..3ac640e 100644 --- a/main.go +++ b/main.go @@ -18,13 +18,24 @@ import ( const ( MAX_CONCURRENT_JOBS = /* Buffer limit for channel */ 10 CODE_REGISTRATION_LIMIT = /* Maximum number of devices that will be registered */ 100 - TIMEOUT = /* Seconds */ 5 + TIMEOUT = /* Seconds */ 30 ) -func main() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() +/* +deveui-cli is a Go CLI program. +It is used for concurrently generating unique 16-character (hex) identifier called a DevEUI. +These are generated by the program and registered via an external (LoRaWAN) API. + +Usage: + + go run main.go (locally) + go run deveui-cli (docker) + +Once this program starts, it will listen to syscall.SIGTERM, syscall.SIGINT via a channel. +This is to handle any unexpected terminations of the program and to resume processing DevEUIs. +*/ +func main() { godotenv.Load(".env") baseurl := os.Getenv("BASE_URL") @@ -45,6 +56,9 @@ func main() { RegisteredDevices: make(chan processor.RegisterDevice), } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + work := make(chan struct{}, MAX_CONCURRENT_JOBS) go func() { for { @@ -53,15 +67,16 @@ func main() { }() // Spawn workers - for j := 0; j < MAX_CONCURRENT_JOBS; j++ { + for job := 0; job < MAX_CONCURRENT_JOBS; job++ { go codeProcessor.Worker(ctx, work) } - n := 0 + // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. + count := 0 for d := range codeProcessor.RegisteredDevices { - fmt.Printf("device: %d has identifier: %s and code: %s\n", n+1, d.Identifier, d.Code) - n += 1 - if n == CODE_REGISTRATION_LIMIT { + fmt.Printf("device: %d has identifier: %s and code: %s\n", count+1, d.Identifier, d.Code) + count += 1 + if count == CODE_REGISTRATION_LIMIT { break } } diff --git a/processor/code_processor.go b/processor/code_processor.go index e2afc58..7c545e5 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -24,6 +24,14 @@ type RegisterDevice struct { Code string } +// Worker attempts to register a valid DevEUI via external LoRaWAN API. +// If successfull, a RegisterDevice struct with it's Identifier and Code will be sent to the work channel. +// +// # Example +// +// Identifier: 1CEB0080F074F750, Code: 4F750 +// +// When an unexpected error occurs, return ctx.Err instead. func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { for { select { @@ -39,13 +47,13 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { } func registerDevice(client client.Client, url string) (bool, RegisterDevice) { - hex, err := codegenerator.GenerateHexString(16) + hex, err := codegenerator.GenerateHexString() if err != nil { log.Print(err) return false, RegisterDevice{} } - code, err := codegenerator.Generate(hex) + code, err := codegenerator.GenerateCode(hex) if err != nil { log.Print(err) return false, RegisterDevice{} From 66b693d8c55fe2a2ef6eadfe4d6d6a3d02e08690 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 21 Apr 2023 08:47:48 +0100 Subject: [PATCH 42/83] more proffessional readme --- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++----- images/logo.jpeg | Bin 0 -> 14394 bytes 2 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 images/logo.jpeg diff --git a/README.md b/README.md index 333c9c6..cb14088 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,41 @@ -# MachineMax deveui-cli +
+
+ + MachineMax Logo + +

DevEUI CLI

-

This is a command line application written in Golang for generation of unique 16-character (hex) identifiers called DevEUI which are used for each MachineMax senor

+

+

A Golang program for concurrently registering DevEUI identifiers for MachineMax.

+

+
+## About MachineMax DevEUI -## How to run this client library +Each MachineMax sensor has a unique 16-character (hex) identifier called a DevEUI. As part of +the manufacturing process, it is written onto the internal storage of the sensor. The DevEUI is +also printed on a label on the side of the sensor alongside a 5-character code (the last 5 +characters of the DevEUI). For example, a DevEUI of 78111FFFE452555B would have a short +code of 2555B. -To run this code, first run `touch .env` and add these vars: +The sensors communicate with the MachineMax cloud though a LoRaWAN provider and the +LoRaWAN provider uses the DevEUI to identify the sensor. This means we first have to register +the DevEUI with the provider before we can use it. We pay for every device registered with the +LoRaWAN provider, so it is important that we only register DevEUIs that we use. + +When a customer registers a new sensor, they will enter the 5-character short-form code instead +of the full DevEUI, so it is essential that each DevEUI in the batch has a unique 5-char code (for +lookups). + +### Built With + +* Golang +* Docker + +## Getting Started + +To run this code, we first need a `.env` file in the root of the project. Once this is done, add these vars: ``` BASE_URL=http://europe-west1-machinemax-dev-d524.cloudfunctions.net @@ -14,11 +43,39 @@ TIMEOUT=30000 CODE_REGISTRATION_LIMIT=100 ``` -Then to run locally, use: `go run main.go`. +### Prerequisites + +- You will need Golang to run this program which can be downloaded at: [https://go.dev/](https://go.dev/) +- This program can also be run using docker, this can be downloaded at: [https://www.docker.com/](https://www.docker.com/) +## Usage -Alternatively, this code can also be run via [Docker](https://www.docker.com/). To build the docker image use: `docker build -t deveui-cli . --build-arg BASE_URL=${BASE_URL} --build-arg TIMEOUT=${TIMEOUT} --build-arg CODE_REGISTRATION_LIMIT=${CODE_REGISTRATION_LIMIT}` (After touch .env and copying env variables from above). +Then to run locally, use: `go run main.go`. + +Alternatively, this code can also be run via [Docker](https://www.docker.com/). To build the docker image use: `docker build -t deveui-cli . --build-arg BASE_URL=${BASE_URL} --build-arg TIMEOUT=${TIMEOUT} --build-arg CODE_REGISTRATION_LIMIT=${CODE_REGISTRATION_LIMIT}`. To run the docker image: `docker run deveui-cli` -To run the tests use: `go test ./...` and to check code coverage: `go test -coverprofile=coverage.out ./... ; go tool cover -html=coverage.out` +To run the tests use: `go test ./...` and to check code coverage: `go test -coverprofile=coverage.out ./... ; go tool cover -html=coverage.out` + +Finally to check for race conditions, use: `go test -race ./...` + +## Roadmap + +- [x] Implement solution +- [x] Write unit tests +- [x] Write readme +- [ ] More unit tests around unhappy paths for higher code coverage + +## Contact + +Nick Gowdy - nickgowdy87@gmail.com + +Website - [www.nickgowdy.com](www.nickgowdy.com) + +Github - [https://github.com/nickgowdy](https://github.com/nickgowdy) + +

(back to top)

+ + + diff --git a/images/logo.jpeg b/images/logo.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..200b7a6caf447cea00a6a8c80b43b0ad24df9259 GIT binary patch literal 14394 zcmeHucT^MI+V9Xii1Zp16a+zO(g}(X0U@IFVnmQCARPn=iWF%PP*4;i9i#-54if2# zB2tuYfY6I1)JWQm@44%{>z;Mj`p&)o-1j@5lRay%O!my|-~67opLs$Zr_KULOpQ&9 z0XjN5;0EmjP^SPxfPtR=ccX2Lw8q5B#Kg$R#LmLP%*x5m$;rXa!NJ9S^avL>FEPCpl4+GJ!{(Nd$ivH zMqZ|)$CS@A^I5vE91q}Exu2fTDt)20L*VKVPDb@+-~%>xK_Oug(Gw?SF%G$=(&fdY*?UuWTr?+9sb5e2Qd5t)_)-TCtSQVT=a~L42&$lanaF-(RK!2My6xR%ty~#vbY5B9ap)} z%6}m}zqW%-TJF8~Gz zkvUq0Ia;V!y6>0LJZTO1cj}U~3Szi_qgwj6>UljrJw&@kgCm zr|DZ6QeL_@fs>GIZBU$TFCW3RXEJQx55;LYuvnRVT-Q&fvbrcg)4p` zK+Tm64!S1J-7m)Bnmar!1Y1k|SW+E=d-W{2@Y**e?Ar#!D-|6k5xsZ|lMNN0gT^^< zSE|Mws0+ap4mJU;I2aX7Jvz6kg0`2DCVp9bMy#z;_`dJu80gi748kGjvpX@oBr*I` zOkA6_TbA~~YlzI_J{8keXTwvRMX?@?~>Dk;jStNp7Def*}spgOP}`Jd;2YE8&7(O`54*uzh;`VyeB>Tb;)& zpZm{7$c*s}MAySRlqnoM07>vgug2GiyXfQ-JCAF+PQ1L8#Y+X~buiN^cdfpcZ(sxS zB|Ths4(69PBJpOk@Hm!4lH;o`k3w?3Voqe_xX1Vg_e_WIWA0&<@Q(~lD4`|vU0wSk zg6n=RL7~darZF9}BD(jkr^vQ8wc|vZ@%iJ#qS|=Lby(bWS=GJauiv zujp-a9Qwhf=|Y}>#}QkW#ua6`2S!#8(t17Af*|T^n72IdiHJ!)?8)Rg{s_h01I3VX zr=ElSdIa+YJ&{LNj0?;m*ed^1aush+RMbAlW$)i3+{gECU(ywstEjpz=jb+uJZf__RQk&QPAv&a3YUmofa2dGfcE(gOt~N=Ji}D?5pLumS3`QOGCknnwAme zwa>>!pQbSBPh&^N*eh&GY8)iQA0xU4bMzIXdtgHPTwdAQ)s1tR5e1Ibzv}f)-fD?I zAz|N1S}Ckvy24Tw?xB7F+vK7GOkyLHbJ2r`jd5yQ+!%s?w?om_j$qN?JUVZ&s23;1 z9Ayg1nSjaV9KyAnwUVW=_Il-PMe+!mpn+5E1%Y(WM-~uNIu#*T>KZgMA?p1Gj4t0) zv!(c&$LLgP%}zmy~u zsX%QA71%9d-$fA=`bf!WPAahelSl;&;@^ba+KkW^`p~R}7_jhJ)j30uP3R>Xn#5j> z?Fu#bQB$~+65@EnRDhFP1K+UhTc1@O%Zm&yqj|E>>ypov`@Me&^b|!R5if zQV!LH(yJ1N6t-o2>*b-J>N*IniQ?imt{~}Do9T4X@|ywZYC6=5X4HyUA86Z@HZLOm zG<+oNM_n$d85Qs%tg6 zk*s!!B!#j8D!`{BC&W?eq%}9noqh>wk<`aP_k;^hkb#q)Ktb?vc^kyxl8&H1Q`(Nq5diOn4$*lcnj(k+y^yfPX%rv zc|Aq1-~tIRh&ID!NX<3)t?m8xnWk+R*ePi#*CB5tgLU{U;*1(17CtuxmxQm_zo98y zeIpeRx-Nt_oP#Gua}$PlI@{&y(mpxV+5PZzeSY$aguv*}tB@I&T~rHtZYzTd49P)B za!@L8e5@B!=g}KWG>CHi*5)^9@HLfmM|Rw3aFRYEKqdlHtD5_KMfqjW_xh5x#;q#W zjJS8-BarGF7#1qv27ZK*&{aH>*aj--fEPo8_kB-~eSII*V|DXfE4{Hqgt&)vefXd{ z@f6L&%nTW46HgM{@0^`X{kAu9_oY*t0mYI%80mxI3nQ?UJ;7S6bYqO($FR!wH%cs1ii=kCDU>y?5_5-Sh9f?eVzm zS5a*gTS9xM;N`sgFbSV+MAsqLK~l)3j`4_;&pWd#O=-iEjO#LK9hQ*hn@rl)TG-H2 zeOWeoklC$`Y`g}xFQK}AkY&XZ;}v?qJXEcuG#?{EUm@VUp>acHTT`Sb5G1J28;+bs zB){mou2gHrWq%_L+n?t4JgV`_NHd#6;(kvwmc&c2b;UW(c_ipcly16>C|aLvR~)nDyxikI zJ~)r=fU}ym}=&2ZN!Ghr(O3@q$)+qVWC^(#X z2HHLRtja!n$xQB9SzpO72IQSYpVb-fu}(~#Z71f)3f>t~wxa#2ZTMu)vD}Ov>CVsX ztH=GWVb~#zqC3lp0l67N61+t({SLqkwA|(tzVD$rD1oBa3=w)Y*LO2(EjUinm{{>H5y*^DP03)n zd+-iIVf%(3Gavcshv{tCE^aeFmBwZI!$eq4mOX!apcfF&?1`EXM{p*sd`>G3eDm@M z1n!zc&L+o!y++Be+J&pKP=QrgXS%BxOB?Bs(eUmtsjz8gF$rP57!jkO?@v~%JvV$T zlr!IgMJ7yZ8IQc5jqNzJt&5T&BlP8?{BZer*@q;s#E;bFcP&m%KaBjBRImZGFb1$t z(flA?+c$!)_ut@qO-R_!`1-R=$kPRfNmZIaEv4!96kE_mCiokoE3wHJP>X%W6Ey4g6_oUXWap@Gc(PKX&Zep<{rAn$(f;6UTeQvKq(;M zc2)vv7M!RU3>9{qobYZuaE5R!S}13m2V0!yDp!DPv3n!RVvW$9%v7Z6TF} zx7|X_%humyy%t?^%QQJe59Q0O8EUR57q$Z4PWI{04V9GogL(9#hwdP}hrQ z&fsB~)_+lIe*6Yhx8L+KcylJ!wK1>#=^i*}_gedXo{5zKUK1AJMEQX6@iB(v^+Nn+1wuT5_3y`~#elqfHV^$3Eio#A1g)Mu& zg63tSzflyv?U}^}mHUWFYz}-L__i3?>Lr5a%c=4%x5~b#C^c&Nj%J$zhr6$96C%s7 zFH+AfVf5Rb$}mKmcE%<2CHO4LMyzftZtNAj;>Fd6NaxP+`&1xJl=Ne%wT23GLG+-h zx!E0<1R=Okv>d^;uaTp z(5f=3MPCUPb^Lopn!c;;xn?eES>-}r*=|oszNwFZkOahLaX9~8E}h=*XE#Pj z*Zh+o6fO!qjL9MpryE zhZZ?e*hGrYYoxRvU%UNkdTQLkvCuX5jfwPG(mbrc9aw7rN)tId(uH~)y3gFPD?6j| z)V$u=KK~}{z3$U8)+AR|(0lH^85rUAaw%SV6g^vgxVUiFze`U-A{*M2Mfqtp*gtGs za~$-#@(m9`BHC{UR)fJ^*_y!E5!P`P7VWr!3U5hi+)Q`&+3`|L^8+J#Ppkyylo%bx zMLa`co5ga(Z#Q{$V{6mnOxIGRcg z&N(9ZczD(fyBangoqYYhTJh=g7|SJBFPX<@xqTRN)|%4~ct}TK6tje6_=Yo_eCh%E z0!1ZyFq)C@%eUk_HdSITylyPR%-XEEYF~(5jOBtJc3kUyoCxJ zUSqo3M-oWBKDv8`hotGNVc9nFIoq~lpmLKpN=B2#OJvPwpj=3P-=fb+)avJ;8x=?h zilV0i`6~V-@#|Cozjnw+)aR1zTI!$BZT0bios^lj5U>T@h;##RFdA2*Ye@wRl~7dR z{xEH{HF0LnUKASNqSLC{>UB2DT}$SdG_uOb(^Rmv*=nIUQUNrWO^O zX*Coee#5sqyu)ES5Rz+$x4Im>Z6rbHmWC#TSiw`p+@XXfDN&LDZj6+YV@tl*Lqn8A z{y{E)FJLGqY4=0DhOf%h)q?qrAnp<9jXwNV7rH^Vqheh#@XL2+XDK2}Vz4_%I^n8r zkH}!Q6elF)MpupOXSgeR0%J*jK?S^!j~qDLZ}rB76dQ&V!Dv8Wejb`v0}j5DDm9n6 zQqH2jP=SeMY&?pnPr5qXW?!so#_79&nPBdmnXHv`^S}TCvW4<_0ZHV~IR9idc%r1x zaFlueG~{0Kn$4#GpTNRQvDw)}@f6zkv-fLxAUYo2aUHy%jzd%4?eUP=f1q`LRZoL)LvhelWB~|w3H*yS>M9kecJQaf zg}`o++{oGO5>Zj3yEOF@^L(?G?GUBN)Y=oNMrZjVfYBJMdpOpM{HN2b;Ioz5@%bKj zj`t7xmzEfPl>$3iZVhfF=!R9B81=WBOi_d<6^(Nz^|NV`bce)9d+w&oRDk&aP6|a! z>H3sRt^80Kt5iJF=i^o8be>)Cw6&nIwTNCg;5GtoR3AKK^#&arLh+T&7w><}z7wi- z;%(;Emh+R_c*v3Z~kJ)3!6cN@AiXx*sEQnik(<9^UO~ua<6qSM#oQC`2bh zi|>McTZ7BA+UeIOYyErMn2jLfv1x+rejC1WvW-V6eb(gCNXhf?ri?k0hRm<&bncqO zN}IM=3R@SxmLfsmuX$LUTcN10tKGJrpI7$fzNo;d2 zf8BSh#6fSfI9h(WRhhkTI;k_PSXOvXE%o&Y*{?f_U~J78`nCc(UQjjGwSeH@ zI5ktMgSsAh^EG@r?k)1ryQ@A5N%Kh&BNy)?ySNOvZzWhZx>B%?H6Q}fmWgJ~cksQ1 zSlE<~4W7B;%!_iL2GO{luAm2BR*x}!rEg-W0YXtUWq;6Y7m>$Ih=@e&X+IsMdHZM? zFwx?*@_tCcqrjzDk0q%3Fi9P!c+kC-C<(%qKe=u+z7HxEfOCqr3}FNqM*1JSaAFvifzig z+&j>zD%0!Evi8Hkvo^8Q4aRI=gcU+jXP3~X;MwQ!H!C9>kRHs@NuGMA3ZpqwTPUbv@(P zM|=aYyt+~PO6pe~I!tY$QM+`jrwo}yTlAQO_^79TZWvS66*|%ik-2)x#+dOzk>tZ8 z#yqBooV_nnzh7LWdByy85D+yW$Vhz1faNcD@lPG7Mqi5D)Xu{5@GvXY1>59~rgUcv zvl=5qIX*7%TYpTDxT6w|`cMV3ZNpJ&Z@u;!V};=;C-ofRhk(hITxc2&C#m# z8n)WH%M$MI|` z2cKgg*tb#FqcF%gnnLk{m`yd``H!0X! z7Vhd$^KG}H`Lw!%$ngB+n&2ew5_gUfw&^&MKe`6+lI zE%_PM;dU{aplh29BFlpGuU}b0yXk5W!=gB9aZ1@PV1$cVQ}Fn!aGHrkaf@H%Px+;} zjwz1T*ny~76}@(t6KUF@X%ns8K2H>xM@^w--3xICb|Y175uA;0SA!h;g!V(mPj7O_ ziJZlji;rQEzca(B+NQaiqs``@0{0UFBueT_2NiB7=_JlfG0qv%Ll<;x*Zq4DQ?av% zq;_t?mJw}z#`UAL+*4yevt|xKXN<^AW)mO`^})h(>Cv#YsQ)WtKOGkJ z10fP0cK^eWKKJS&`au-D;#}@r{7K^F_Hegnj+Za(xJ)K}7T6ZQapSDt7L0obqJHf` zxulKU$DSDwZK2q4NnbCV2b)zsdn$?tRnjW?f@^K&jiWiw6Q}pr$TyXOYFj*nQ7JTy z(kf*wgFI!2KA%B)!aFpg^}{)4eU}lKK1nHawh+w?o3HQMMj}d71KOfD#e{#n3wVjo z^Vy^V1b-?J1KYilPrI4#78MXd_0U$zHF=V%*{bg8W%HGY$^X-!LE1sN(q05J$(N8j zS7qDbFzk~hm;;`0e=e0Jkr6R`Zu(LZl#|<_PklQm(2_3|>t8d_j%a_UjSg+`Jty>Z;Wf4W=;@a_V&!`hu&5#G^`-$B6%p z*7~QLKYbVMZ|0mkmOlD`RWDIWe5{6KO0JDoB3;L&4#w+R&v%urgef7^D?O zbVq|}_G4h!!uy_a(P)HW4q2C+0{4R6N77N~R&tn2vB{C<(TfYCA$DWX)|-t-kDSY> zd2HHSTXJ4n{u1i5zUNY|sdvF!)=y7oB;OJ3qnwkmVSR%F;d%X`O+%9bUM00QS9}Yu zq}Q-88P2wIvnqv4D14yZQmg8Cvvs;oE3z6!kDh%6Vn@$e5KPaAhWDsp_@~ApzGaRB zw?Y&|q?uI~GfrM>lZ$2}&a~JrQh{Da80Tb^72cGfdIfO}R{{H|^df*CD*B5%?6q%* z4O7_d1de8tJpK^h2gG1AGEU(e9EXmr_j8ZudUU{MpUk}QRB!@m(*OV(9BUFXE{YIa%NIZ03DT#MPgvV!1`9rUOj{w_sUO zLj%^P0cDjvBATs*E10QFD}oD_Cz+)e#Mx%(U*Wi(=>F~WTzwt`)7ReBfa6LRU&Ui( z?*s;ihrz literal 0 HcmV?d00001 From e9f068470cd3321f82cf2b607634d6c5e426b44e Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 21 Apr 2023 08:51:05 +0100 Subject: [PATCH 43/83] Links now working --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cb14088..4a1a84c 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,9 @@ Finally to check for race conditions, use: `go test -race ./...` Nick Gowdy - nickgowdy87@gmail.com -Website - [www.nickgowdy.com](www.nickgowdy.com) +Website http://www.nickgowdy.com/ -Github - [https://github.com/nickgowdy](https://github.com/nickgowdy) +Github https://github.com/nickgowdy

(back to top)

From 5475149296ea1aca2e86567673729fc1d8842c4f Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 21 Apr 2023 08:56:52 +0100 Subject: [PATCH 44/83] final changes --- README.md | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4a1a84c..b69e0ef 100644 --- a/README.md +++ b/README.md @@ -52,15 +52,31 @@ CODE_REGISTRATION_LIMIT=100 Then to run locally, use: `go run main.go`. -Alternatively, this code can also be run via [Docker](https://www.docker.com/). To build the docker image use: `docker build -t deveui-cli . --build-arg BASE_URL=${BASE_URL} --build-arg TIMEOUT=${TIMEOUT} --build-arg CODE_REGISTRATION_LIMIT=${CODE_REGISTRATION_LIMIT}`. +Alternatively, this code can also be run via docker. To build the docker image use: +``` +docker build -t deveui-cli . --build-arg BASE_URL=${BASE_URL} --build-arg TIMEOUT=${TIMEOUT} --build-arg CODE_REGISTRATION_LIMIT=${CODE_REGISTRATION_LIMIT}. +``` -To run the docker image: `docker run deveui-cli` +To run the docker image: +``` +docker run deveui-cli +``` -To run the tests use: `go test ./...` and to check code coverage: `go test -coverprofile=coverage.out ./... ; go tool cover -html=coverage.out` +To run the tests use: +``` +go test ./... +``` +and to check code coverage: +``` +go test -coverprofile=coverage.out ./... ; go tool cover -html=coverage.out +``` -Finally to check for race conditions, use: `go test -race ./...` +Finally to check for race conditions, use: +``` +go test -race ./... +``` -## Roadmap +## Checklist - [x] Implement solution - [x] Write unit tests @@ -69,13 +85,12 @@ Finally to check for race conditions, use: `go test -race ./...` ## Contact -Nick Gowdy - nickgowdy87@gmail.com +Email - nickgowdy87@gmail.com Website http://www.nickgowdy.com/ Github https://github.com/nickgowdy -

(back to top)

From 96513d188a0c7aaae8cc7ae8115acb6fe8b1b8c3 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 21 Apr 2023 15:04:23 +0100 Subject: [PATCH 45/83] Close channels once done --- main.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 3ac640e..fa906b3 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,20 @@ func main() { defer cancel() work := make(chan struct{}, MAX_CONCURRENT_JOBS) + listener := make(chan os.Signal, 1) + + // goroutine to listen for syscall.SIGTERM, syscall.SIGINT + go func() { + signal.Notify(listener, syscall.SIGTERM, syscall.SIGINT) + go func() { + for { + time.Sleep(1000) + } + }() + sig := <-listener + log.Printf("Caught signal %v", sig) + }() + go func() { for { work <- struct{}{} @@ -81,16 +95,6 @@ func main() { } } - // goroutine to listen for syscall.SIGTERM, syscall.SIGINT - go func() { - cancelChan := make(chan os.Signal, 1) - signal.Notify(cancelChan, syscall.SIGTERM, syscall.SIGINT) - go func() { - for { - time.Sleep(1000) - } - }() - sig := <-cancelChan - log.Printf("Caught signal %v", sig) - }() + close(work) + close(listener) } From bde4c8394fcd86dc4dffab6268939509ca6a64cd Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 09:42:27 +0100 Subject: [PATCH 46/83] refactor device logic --- .../code_generator.go => device/device.go | 26 +++++++++++---- .../device_test.go | 33 +++++++++---------- main.go | 5 +-- processor/code_processor.go | 33 +++++-------------- processor/code_processor_test.go | 6 ++-- 5 files changed, 52 insertions(+), 51 deletions(-) rename codegenerator/code_generator.go => device/device.go (59%) rename codegenerator/code_generator_test.go => device/device_test.go (63%) diff --git a/codegenerator/code_generator.go b/device/device.go similarity index 59% rename from codegenerator/code_generator.go rename to device/device.go index a9eb7ed..9a16723 100644 --- a/codegenerator/code_generator.go +++ b/device/device.go @@ -1,7 +1,8 @@ -package codegenerator +package device import ( "crypto/rand" + "log" "math/big" ) @@ -10,13 +11,26 @@ const ( DEV_EUI_LENGTH = 16 // valid DevEUI is string of length 16 ) -// Generate valid DevEUI code from DevEUI identifier. +type Device struct { + Identifier string + Code string +} + +// Build new device with DevEUI identifier and code values. // // # Example // -// 1CEB0080F074F750 will return 4F750 -func GenerateCode(hex string) (string, error) { - return hex[len(hex)-5:], nil +// 1CEB0080F074F750 4F750 +func NewDevice() *Device { + hex, err := generateHexString() + if err != nil { + log.Print(err) + } + + return &Device{ + Identifier: hex, + Code: hex[len(hex)-5:], + } } // Generate valid DevEUI identifier value. @@ -24,7 +38,7 @@ func GenerateCode(hex string) (string, error) { // # Example // // 1CEB0080F074F750 -func GenerateHexString() (string, error) { +func generateHexString() (string, error) { max := big.NewInt(int64(len(ALLOWED_CHARS))) b := make([]byte, DEV_EUI_LENGTH) for i := range b { diff --git a/codegenerator/code_generator_test.go b/device/device_test.go similarity index 63% rename from codegenerator/code_generator_test.go rename to device/device_test.go index 7e9cb1e..78ff189 100644 --- a/codegenerator/code_generator_test.go +++ b/device/device_test.go @@ -1,4 +1,4 @@ -package codegenerator +package device import ( "testing" @@ -6,20 +6,19 @@ import ( func TestCanGenerateValidCode(t *testing.T) { allowedChars := []string{"A", "B", "C", "D", "E", "F", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} - hex, _ := GenerateHexString() - code, err := GenerateCode(hex) + device := NewDevice() - if err != nil { - t.Errorf("error should be nil, but is %s", err.Error()) + if device == nil { + t.Errorf("deivce should not be nil, but is %s", device) } - if len(code) != 5 { - t.Errorf("code should be 5 characters long, but is %d", len(code)) + if len(device.Code) != 5 { + t.Errorf("code should be 5 characters long, but is %d", len(device.Code)) } hasChar := false for _, char := range allowedChars { - if char == string(code[0]) { + if char == string(device.Code[0]) { hasChar = true } else if hasChar { break @@ -27,12 +26,12 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("first char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[0])) + t.Errorf("first char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[0])) } hasChar = false for _, char := range allowedChars { - if char == string(code[1]) { + if char == string(device.Code[1]) { hasChar = true } else if hasChar { break @@ -40,12 +39,12 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("second char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[1])) + t.Errorf("second char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[1])) } hasChar = false for _, char := range allowedChars { - if char == string(code[2]) { + if char == string(device.Code[2]) { hasChar = true } else if hasChar { break @@ -53,12 +52,12 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("third char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[2])) + t.Errorf("third char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[2])) } hasChar = false for _, char := range allowedChars { - if char == string(code[3]) { + if char == string(device.Code[3]) { hasChar = true } else if hasChar { break @@ -66,12 +65,12 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("fourth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[3])) + t.Errorf("fourth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[3])) } hasChar = false for _, char := range allowedChars { - if char == string(code[4]) { + if char == string(device.Code[4]) { hasChar = true } else if hasChar { break @@ -79,6 +78,6 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("fifth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[4])) + t.Errorf("fifth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[4])) } } diff --git a/main.go b/main.go index fa906b3..bbe7ff2 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "time" "github.com/NickGowdy/deveui-cli/client" + "github.com/NickGowdy/deveui-cli/device" "github.com/NickGowdy/deveui-cli/processor" "github.com/joho/godotenv" ) @@ -53,7 +54,7 @@ func main() { BaseUrl: baseurl, CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, Client: loraWanClient, - RegisteredDevices: make(chan processor.RegisterDevice), + Device: make(chan device.Device), } ctx, cancel := context.WithCancel(context.Background()) @@ -87,7 +88,7 @@ func main() { // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. count := 0 - for d := range codeProcessor.RegisteredDevices { + for d := range codeProcessor.Device { fmt.Printf("device: %d has identifier: %s and code: %s\n", count+1, d.Identifier, d.Code) count += 1 if count == CODE_REGISTRATION_LIMIT { diff --git a/processor/code_processor.go b/processor/code_processor.go index 7c545e5..8a8a051 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -8,7 +8,7 @@ import ( "net/http" "github.com/NickGowdy/deveui-cli/client" - "github.com/NickGowdy/deveui-cli/codegenerator" + "github.com/NickGowdy/deveui-cli/device" ) type CodeProcessor struct { @@ -16,12 +16,7 @@ type CodeProcessor struct { MaxConcurrentJobs int BaseUrl string Client client.Client - RegisteredDevices chan RegisterDevice -} - -type RegisterDevice struct { - Identifier string - Code string + Device chan device.Device } // Worker attempts to register a valid DevEUI via external LoRaWAN API. @@ -40,29 +35,19 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { case <-work: saved, registeredDevice := registerDevice(cp.Client, cp.BaseUrl) if saved { - cp.RegisteredDevices <- registeredDevice + cp.Device <- *registeredDevice } } } } -func registerDevice(client client.Client, url string) (bool, RegisterDevice) { - hex, err := codegenerator.GenerateHexString() - if err != nil { - log.Print(err) - return false, RegisterDevice{} - } - - code, err := codegenerator.GenerateCode(hex) - if err != nil { - log.Print(err) - return false, RegisterDevice{} - } +func registerDevice(client client.Client, url string) (bool, *device.Device) { + device := device.NewDevice() b := new(bytes.Buffer) - reqBody := map[string]string{"Deveui": code} + reqBody := map[string]string{"Deveui": device.Code} - err = json.NewEncoder(b).Encode(&reqBody) + err := json.NewEncoder(b).Encode(&reqBody) if err != nil { log.Print(err) } @@ -76,8 +61,8 @@ func registerDevice(client client.Client, url string) (bool, RegisterDevice) { defer resp.Body.Close() if resp.StatusCode == http.StatusOK { - return true, RegisterDevice{Code: code, Identifier: hex} + return true, device } else { - return false, RegisterDevice{} + return false, nil } } diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index c8f453a..f29a7f1 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -7,6 +7,8 @@ import ( "io" "net/http" "testing" + + "github.com/NickGowdy/deveui-cli/device" ) type MockClient struct { @@ -33,7 +35,7 @@ func TestCanProcessCodes(t *testing.T) { MaxConcurrentJobs: 10, BaseUrl: "http://www.mock-url.com", Client: client, - RegisteredDevices: make(chan RegisterDevice), + Device: make(chan device.Device), } ctx, cancel := context.WithCancel(context.Background()) @@ -53,7 +55,7 @@ func TestCanProcessCodes(t *testing.T) { } n := 0 - for d := range codeProcessor.RegisteredDevices { + for d := range codeProcessor.Device { fmt.Printf("device: %d has identifier: %s and code: %s\n", n+1, d.Identifier, d.Code) n += 1 if n == 10 { From 213896b5fe19b1424184273d441e81f16ea6eb2c Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 10:00:44 +0100 Subject: [PATCH 47/83] I think these are last changes --- device/device.go | 2 +- device/device_test.go | 4 ++++ main.go | 2 ++ processor/code_processor.go | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/device/device.go b/device/device.go index 9a16723..c1fb085 100644 --- a/device/device.go +++ b/device/device.go @@ -24,7 +24,7 @@ type Device struct { func NewDevice() *Device { hex, err := generateHexString() if err != nil { - log.Print(err) + log.Fatal(err) } return &Device{ diff --git a/device/device_test.go b/device/device_test.go index 78ff189..700a890 100644 --- a/device/device_test.go +++ b/device/device_test.go @@ -16,6 +16,10 @@ func TestCanGenerateValidCode(t *testing.T) { t.Errorf("code should be 5 characters long, but is %d", len(device.Code)) } + if device.Identifier[len(device.Identifier)-5:] != device.Code { + t.Errorf("code should be last 5 characters of identifier, but is %s", device.Code) + } + hasChar := false for _, char := range allowedChars { if char == string(device.Code[0]) { diff --git a/main.go b/main.go index bbe7ff2..ee0f25a 100644 --- a/main.go +++ b/main.go @@ -75,6 +75,7 @@ func main() { log.Printf("Caught signal %v", sig) }() + // Fill work buffer so we can start processing work go func() { for { work <- struct{}{} @@ -96,6 +97,7 @@ func main() { } } + // Channels have finished work, close them in main process close(work) close(listener) } diff --git a/processor/code_processor.go b/processor/code_processor.go index 8a8a051..60ff29a 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -45,7 +45,7 @@ func registerDevice(client client.Client, url string) (bool, *device.Device) { device := device.NewDevice() b := new(bytes.Buffer) - reqBody := map[string]string{"Deveui": device.Code} + reqBody := map[string]string{"Deveui": device.Identifier} err := json.NewEncoder(b).Encode(&reqBody) if err != nil { From 8420c364d2e289fb5cfa136462556cf984bbba6d Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 10:05:09 +0100 Subject: [PATCH 48/83] Only listen to interrupts --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index ee0f25a..ceea52c 100644 --- a/main.go +++ b/main.go @@ -63,9 +63,9 @@ func main() { work := make(chan struct{}, MAX_CONCURRENT_JOBS) listener := make(chan os.Signal, 1) - // goroutine to listen for syscall.SIGTERM, syscall.SIGINT + // goroutine to listen for syscall.SIGINT go func() { - signal.Notify(listener, syscall.SIGTERM, syscall.SIGINT) + signal.Notify(listener, syscall.SIGINT) go func() { for { time.Sleep(1000) From 04c942861fc63f932b5b57eb98a2803301348a8c Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 10:18:29 +0100 Subject: [PATCH 49/83] close here to prevent panic --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index ceea52c..b32f7ce 100644 --- a/main.go +++ b/main.go @@ -93,11 +93,11 @@ func main() { fmt.Printf("device: %d has identifier: %s and code: %s\n", count+1, d.Identifier, d.Code) count += 1 if count == CODE_REGISTRATION_LIMIT { + // close channels now that work is done + close(work) + close(listener) break } } - // Channels have finished work, close them in main process - close(work) - close(listener) } From baa96bcc296239929b3ed6343bee057283540956 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 10:18:55 +0100 Subject: [PATCH 50/83] benchmark perf --- main_test.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 main_test.go diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..88cec9f --- /dev/null +++ b/main_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func BenchmarkMain(b *testing.B) { + main() +} From 5b3a2cb3e129d644c989689b18a480182c612761 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 10:24:01 +0100 Subject: [PATCH 51/83] Added benchmark to readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b69e0ef..3e65db1 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,16 @@ To run the tests use: ``` go test ./... ``` -and to check code coverage: +To check code coverage: ``` go test -coverprofile=coverage.out ./... ; go tool cover -html=coverage.out ``` +And to run benchmark tests for CPU and memory consumption: +``` +go test -bench=. -benchmem +``` + Finally to check for race conditions, use: ``` go test -race ./... From 9da4a364cb3a59f21ea73f50c91b01f2db975f28 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 10:32:27 +0100 Subject: [PATCH 52/83] don't need to close channel, GC will handle it --- main.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/main.go b/main.go index b32f7ce..d38928c 100644 --- a/main.go +++ b/main.go @@ -93,11 +93,7 @@ func main() { fmt.Printf("device: %d has identifier: %s and code: %s\n", count+1, d.Identifier, d.Code) count += 1 if count == CODE_REGISTRATION_LIMIT { - // close channels now that work is done - close(work) - close(listener) break } } - } From 297b7aa0ba946689d1e0688bac0737032f8c7d17 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 10:33:27 +0100 Subject: [PATCH 53/83] done --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3e65db1..c8dccee 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ go test -race ./... - [x] Write unit tests - [x] Write readme - [ ] More unit tests around unhappy paths for higher code coverage +- [ ] Discuss with engineers how to improve code ## Contact From 6762a65a875cb52b1b52bd095302d99206e04b0c Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sun, 23 Apr 2023 10:46:49 +0100 Subject: [PATCH 54/83] more assertions --- processor/code_processor_test.go | 38 ++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index f29a7f1..7528101 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -3,7 +3,6 @@ package processor import ( "bytes" "context" - "fmt" "io" "net/http" "testing" @@ -23,6 +22,11 @@ func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp nil } +const ( + MAX_CONCURRENT_JOBS = 2 + CODE_REGISTRATION_LIMIT = 10 +) + func TestCanProcessCodes(t *testing.T) { client := &MockClient{ DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { @@ -31,8 +35,8 @@ func TestCanProcessCodes(t *testing.T) { } codeProcessor := &CodeProcessor{ - CodeRegistrationLimit: 10, - MaxConcurrentJobs: 10, + CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, + MaxConcurrentJobs: MAX_CONCURRENT_JOBS, BaseUrl: "http://www.mock-url.com", Client: client, Device: make(chan device.Device), @@ -41,7 +45,7 @@ func TestCanProcessCodes(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - work := make(chan struct{}, 10) + work := make(chan struct{}, MAX_CONCURRENT_JOBS) go func() { for { @@ -50,15 +54,35 @@ func TestCanProcessCodes(t *testing.T) { }() // Spawn workers - for j := 0; j < 10; j++ { + for j := 0; j < MAX_CONCURRENT_JOBS; j++ { go codeProcessor.Worker(ctx, work) } n := 0 for d := range codeProcessor.Device { - fmt.Printf("device: %d has identifier: %s and code: %s\n", n+1, d.Identifier, d.Code) + + if d.Code == "" { + t.Error("code should not be nil") + } + + if d.Identifier == "" { + t.Error("identifier should not be nil") + } + + if d.Identifier[len(d.Identifier)-5:] != d.Code { + t.Errorf("code should be last 5 characters of identifier, but is %s", d.Code) + } + + if len(d.Identifier) != 16 { + t.Errorf("identifier should be exactly 16 characters, but is %d", len(d.Identifier)) + } + + if len(d.Code) != 5 { + t.Errorf("code should be exactly 5 characters, but is %d", len(d.Code)) + } + n += 1 - if n == 10 { + if n == CODE_REGISTRATION_LIMIT { break } } From ec15adcd69267a027f0625568c1b426679328585 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 4 May 2023 13:31:44 +0100 Subject: [PATCH 55/83] Move interface to lorawan_client.go --- client/client.go | 11 ----------- client/lorawan_client.go | 5 +++++ 2 files changed, 5 insertions(+), 11 deletions(-) delete mode 100644 client/client.go diff --git a/client/client.go b/client/client.go deleted file mode 100644 index e4f580d..0000000 --- a/client/client.go +++ /dev/null @@ -1,11 +0,0 @@ -package client - -import ( - "io" - "net/http" -) - -// Generic client used to communicate to external services -type Client interface { - Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) -} diff --git a/client/lorawan_client.go b/client/lorawan_client.go index 79c2f7b..6d66341 100644 --- a/client/lorawan_client.go +++ b/client/lorawan_client.go @@ -6,6 +6,11 @@ import ( "net/http" ) +// Generic client used to communicate to external services +type Client interface { + Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) +} + // Client used to communicate to LoRaWAN external system type LoraWanClient struct { Client Client From 7668633bc2f647708b56af1ac87022a8e0c074fb Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 4 May 2023 14:07:26 +0100 Subject: [PATCH 56/83] NewLoraWAN func to create new client --- client/lorawan_client.go | 14 ++++++++++---- client/lorawan_client_test.go | 4 +--- main.go | 5 ++--- processor/code_processor_test.go | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/client/lorawan_client.go b/client/lorawan_client.go index 6d66341..462f1ba 100644 --- a/client/lorawan_client.go +++ b/client/lorawan_client.go @@ -12,16 +12,22 @@ type Client interface { } // Client used to communicate to LoRaWAN external system -type LoraWanClient struct { - Client Client +type LoraWAN struct { + client Client +} + +func NewLoraWAN(client Client) *LoraWAN { + return &LoraWAN{ + client: client, + } } const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN // Send data via POST (HTTP) request -func (h *LoraWanClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { +func (l *LoraWAN) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { fullUrl := fmt.Sprintf("%s/%s", url, endpoint) - resp, err = h.Client.Post(fullUrl, contentType, body) + resp, err = l.client.Post(fullUrl, contentType, body) if err != nil { return nil, err } diff --git a/client/lorawan_client_test.go b/client/lorawan_client_test.go index e7b8cd5..d3b39e8 100644 --- a/client/lorawan_client_test.go +++ b/client/lorawan_client_test.go @@ -19,9 +19,7 @@ func TestLorawanClientHappyPath(t *testing.T) { }, } - loraWanClient := LoraWanClient{ - Client: mockClient, - } + loraWanClient := NewLoraWAN(mockClient) b := new(bytes.Buffer) reqBody := map[string]string{"Deveui": "Abcde"} diff --git a/main.go b/main.go index d38928c..23109f6 100644 --- a/main.go +++ b/main.go @@ -44,9 +44,8 @@ func main() { httpClient := &http.Client{ Timeout: time.Second * TIMEOUT, } - loraWanClient := &client.LoraWanClient{ - Client: httpClient, - } + + loraWanClient := client.NewLoraWAN(httpClient) // setup processor to do work codeProcessor := &processor.CodeProcessor{ diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 7528101..1fe7a01 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -11,7 +11,7 @@ import ( ) type MockClient struct { - DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) + DoPost func(body io.Reader) (resp *http.Response, err error) } func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { @@ -29,7 +29,7 @@ const ( func TestCanProcessCodes(t *testing.T) { client := &MockClient{ - DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + DoPost: func(body io.Reader) (resp *http.Response, err error) { return &http.Response{}, nil }, } From 87d8798b27ac315409ed2e9bee7326a2f17f9c00 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 4 May 2023 14:08:28 +0100 Subject: [PATCH 57/83] rename lorawan file --- client/{lorawan_client.go => lorawan.go} | 0 client/{lorawan_client_test.go => lorawan_test.go} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename client/{lorawan_client.go => lorawan.go} (100%) rename client/{lorawan_client_test.go => lorawan_test.go} (95%) diff --git a/client/lorawan_client.go b/client/lorawan.go similarity index 100% rename from client/lorawan_client.go rename to client/lorawan.go diff --git a/client/lorawan_client_test.go b/client/lorawan_test.go similarity index 95% rename from client/lorawan_client_test.go rename to client/lorawan_test.go index d3b39e8..9264a79 100644 --- a/client/lorawan_client_test.go +++ b/client/lorawan_test.go @@ -12,7 +12,7 @@ type MockClient struct { DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) } -func TestLorawanClientHappyPath(t *testing.T) { +func TestLorawanHappyPath(t *testing.T) { mockClient := &MockClient{ DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { return &http.Response{}, nil From 52ad9f4da08ef8be1933089873bd870bb41da485 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 4 May 2023 14:12:11 +0100 Subject: [PATCH 58/83] Use path.Join instead of fmt --- client/lorawan.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index 462f1ba..c8eda79 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -1,14 +1,14 @@ package client import ( - "fmt" "io" "net/http" + "path" ) // Generic client used to communicate to external services type Client interface { - Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) + Post(baseURL string, contentType string, body io.Reader) (resp *http.Response, err error) } // Client used to communicate to LoRaWAN external system @@ -25,8 +25,8 @@ func NewLoraWAN(client Client) *LoraWAN { const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN // Send data via POST (HTTP) request -func (l *LoraWAN) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { - fullUrl := fmt.Sprintf("%s/%s", url, endpoint) +func (l *LoraWAN) Post(baseURL string, contentType string, body io.Reader) (resp *http.Response, err error) { + fullUrl := path.Join(baseURL, endpoint) resp, err = l.client.Post(fullUrl, contentType, body) if err != nil { return nil, err From 7158f46903cf5f5d620c67eea6dda23a95daade3 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 4 May 2023 15:07:33 +0100 Subject: [PATCH 59/83] interface changes --- client/lorawan.go | 22 +++++++++++++--------- client/lorawan_test.go | 10 +++++----- processor/code_processor.go | 2 +- processor/code_processor_test.go | 2 +- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index c8eda79..19ee523 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -4,30 +4,34 @@ import ( "io" "net/http" "path" + "time" ) -// Generic client used to communicate to external services +// Client used to communicate to external services type Client interface { - Post(baseURL string, contentType string, body io.Reader) (resp *http.Response, err error) + Post(body io.Reader) (resp *http.Response, err error) } -// Client used to communicate to LoRaWAN external system +// LoraWAN used to communicate to LoRaWAN external system type LoraWAN struct { - client Client + client http.Client + baseURL string } -func NewLoraWAN(client Client) *LoraWAN { +func NewLoraWAN(baseURL string, timeout time.Duration) *LoraWAN { return &LoraWAN{ - client: client, + client: http.Client{ + Timeout: timeout * time.Second, + }, } } const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN // Send data via POST (HTTP) request -func (l *LoraWAN) Post(baseURL string, contentType string, body io.Reader) (resp *http.Response, err error) { - fullUrl := path.Join(baseURL, endpoint) - resp, err = l.client.Post(fullUrl, contentType, body) +func (l *LoraWAN) Post(body io.Reader) (resp *http.Response, err error) { + fullUrl := path.Join(l.baseURL, endpoint) + resp, err = l.client.Post(fullUrl, "application/json", body) if err != nil { return nil, err } diff --git a/client/lorawan_test.go b/client/lorawan_test.go index 9264a79..2f1ea00 100644 --- a/client/lorawan_test.go +++ b/client/lorawan_test.go @@ -9,24 +9,24 @@ import ( ) type MockClient struct { - DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) + DoPost func(body io.Reader) (resp *http.Response, err error) } func TestLorawanHappyPath(t *testing.T) { mockClient := &MockClient{ - DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + DoPost: func(body io.Reader) (resp *http.Response, err error) { return &http.Response{}, nil }, } - loraWanClient := NewLoraWAN(mockClient) + // loraWanClient := NewLoraWAN("mock-url", 30000) b := new(bytes.Buffer) reqBody := map[string]string{"Deveui": "Abcde"} _ = json.NewEncoder(b).Encode(&reqBody) - resp, err := loraWanClient.Post("mock-url", "application/json", b) + resp, err := mockClient.Post(b) if err != nil { t.Errorf("err should be nil but is: %s", err.Error()) @@ -37,7 +37,7 @@ func TestLorawanHappyPath(t *testing.T) { } } -func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { +func (m *MockClient) Post(body io.Reader) (resp *http.Response, err error) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(nil)), diff --git a/processor/code_processor.go b/processor/code_processor.go index 60ff29a..d8cfd8d 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -52,7 +52,7 @@ func registerDevice(client client.Client, url string) (bool, *device.Device) { log.Print(err) } - resp, err := client.Post(url, "application/json", b) + resp, err := client.Post(b) if err != nil { log.Fatal(err) diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 1fe7a01..a7517fe 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -14,7 +14,7 @@ type MockClient struct { DoPost func(body io.Reader) (resp *http.Response, err error) } -func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { +func (m *MockClient) Post(body io.Reader) (resp *http.Response, err error) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(nil)), From e58835bcab6570f4d19a30362305806826b8a256 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Thu, 4 May 2023 15:07:45 +0100 Subject: [PATCH 60/83] refactor --- main.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index 23109f6..40198b0 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "net/http" "os" "os/signal" "syscall" @@ -19,7 +18,7 @@ import ( const ( MAX_CONCURRENT_JOBS = /* Buffer limit for channel */ 10 CODE_REGISTRATION_LIMIT = /* Maximum number of devices that will be registered */ 100 - TIMEOUT = /* Seconds */ 30 + TIMEOUT = /* Milliseconds */ 30000 ) /* @@ -40,19 +39,14 @@ func main() { godotenv.Load(".env") baseurl := os.Getenv("BASE_URL") - // setup client for requests - httpClient := &http.Client{ - Timeout: time.Second * TIMEOUT, - } - - loraWanClient := client.NewLoraWAN(httpClient) + loraWAN := client.NewLoraWAN(baseurl, TIMEOUT) // setup processor to do work codeProcessor := &processor.CodeProcessor{ MaxConcurrentJobs: MAX_CONCURRENT_JOBS, BaseUrl: baseurl, CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, - Client: loraWanClient, + Client: loraWAN, Device: make(chan device.Device), } From f3e42832b7634408ecf8b0b645ba14910226a48e Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Mon, 8 May 2023 14:20:04 +0100 Subject: [PATCH 61/83] pass in interface to lorawan post --- client/lorawan.go | 13 ++++++------- client/lorawan_test.go | 29 ++++++++++++++++++++++------- main.go | 4 ++-- processor/code_processor.go | 8 ++++---- processor/code_processor_test.go | 16 ++++++++++------ 5 files changed, 44 insertions(+), 26 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index 19ee523..7d8dda4 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -9,29 +9,28 @@ import ( // Client used to communicate to external services type Client interface { - Post(body io.Reader) (resp *http.Response, err error) + Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) } // LoraWAN used to communicate to LoRaWAN external system type LoraWAN struct { - client http.Client + timeout time.Duration baseURL string } func NewLoraWAN(baseURL string, timeout time.Duration) *LoraWAN { return &LoraWAN{ - client: http.Client{ - Timeout: timeout * time.Second, - }, + baseURL: baseURL, + timeout: timeout, } } const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN // Send data via POST (HTTP) request -func (l *LoraWAN) Post(body io.Reader) (resp *http.Response, err error) { +func (l *LoraWAN) DoPost(client Client, body io.Reader) (resp *http.Response, err error) { fullUrl := path.Join(l.baseURL, endpoint) - resp, err = l.client.Post(fullUrl, "application/json", body) + resp, err = client.Post(fullUrl, "application/json", body) if err != nil { return nil, err } diff --git a/client/lorawan_test.go b/client/lorawan_test.go index 2f1ea00..57ca717 100644 --- a/client/lorawan_test.go +++ b/client/lorawan_test.go @@ -4,43 +4,58 @@ import ( "bytes" "encoding/json" "io" + "net/http" + "strings" "testing" + "time" ) type MockClient struct { - DoPost func(body io.Reader) (resp *http.Response, err error) + DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) } -func TestLorawanHappyPath(t *testing.T) { +func TestLorawanClientHappyPath(t *testing.T) { mockClient := &MockClient{ - DoPost: func(body io.Reader) (resp *http.Response, err error) { + DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { return &http.Response{}, nil }, } - // loraWanClient := NewLoraWAN("mock-url", 30000) + loraWanClient := NewLoraWAN("www.example.com", time.Microsecond*30000) b := new(bytes.Buffer) reqBody := map[string]string{"Deveui": "Abcde"} _ = json.NewEncoder(b).Encode(&reqBody) - resp, err := mockClient.Post(b) + resp, err := loraWanClient.DoPost(mockClient, b) if err != nil { t.Errorf("err should be nil but is: %s", err.Error()) } + defer resp.Body.Close() if resp.StatusCode != 200 { t.Errorf("resp should be nil but is: %d", resp.StatusCode) } + + body, _ := io.ReadAll(resp.Body) + val := string(body) + + if strings.TrimSpace(val) != "true" { + t.Errorf("body should equal true but is: %d", body) + } } -func (m *MockClient) Post(body io.Reader) (resp *http.Response, err error) { +func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + b := new(bytes.Buffer) + reqBody := true + + _ = json.NewEncoder(b).Encode(&reqBody) return &http.Response{ StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(nil)), + Body: io.NopCloser(b), Status: "200 OK"}, nil } diff --git a/main.go b/main.go index 40198b0..c93b180 100644 --- a/main.go +++ b/main.go @@ -43,10 +43,10 @@ func main() { // setup processor to do work codeProcessor := &processor.CodeProcessor{ + CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, MaxConcurrentJobs: MAX_CONCURRENT_JOBS, BaseUrl: baseurl, - CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, - Client: loraWAN, + LoraWAN: *loraWAN, Device: make(chan device.Device), } diff --git a/processor/code_processor.go b/processor/code_processor.go index d8cfd8d..47439a6 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -15,7 +15,7 @@ type CodeProcessor struct { CodeRegistrationLimit int MaxConcurrentJobs int BaseUrl string - Client client.Client + LoraWAN client.LoraWAN Device chan device.Device } @@ -33,7 +33,7 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { case <-ctx.Done(): return ctx.Err() case <-work: - saved, registeredDevice := registerDevice(cp.Client, cp.BaseUrl) + saved, registeredDevice := registerDevice(cp.LoraWAN) if saved { cp.Device <- *registeredDevice } @@ -41,7 +41,7 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { } } -func registerDevice(client client.Client, url string) (bool, *device.Device) { +func registerDevice(loraWAN client.LoraWAN) (bool, *device.Device) { device := device.NewDevice() b := new(bytes.Buffer) @@ -52,7 +52,7 @@ func registerDevice(client client.Client, url string) (bool, *device.Device) { log.Print(err) } - resp, err := client.Post(b) + resp, err := loraWAN.DoPost(&http.Client{}, b) if err != nil { log.Fatal(err) diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index a7517fe..2543810 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -6,7 +6,9 @@ import ( "io" "net/http" "testing" + "time" + "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/device" ) @@ -28,18 +30,20 @@ const ( ) func TestCanProcessCodes(t *testing.T) { - client := &MockClient{ - DoPost: func(body io.Reader) (resp *http.Response, err error) { - return &http.Response{}, nil - }, - } + // client := &MockClient{ + // DoPost: func(body io.Reader) (resp *http.Response, err error) { + // return &http.Response{}, nil + // }, + // } + + loraWAN := client.NewLoraWAN("http://www.mock-url.com", time.Microsecond*30000) codeProcessor := &CodeProcessor{ CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, MaxConcurrentJobs: MAX_CONCURRENT_JOBS, BaseUrl: "http://www.mock-url.com", - Client: client, Device: make(chan device.Device), + LoraWAN: *loraWAN, } ctx, cancel := context.WithCancel(context.Background()) From c54c8e367fe1ff867a6685a0340a1f6f367f43be Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Mon, 8 May 2023 14:56:11 +0100 Subject: [PATCH 62/83] I can set custom timeout this way --- client/lorawan.go | 14 ++++++-------- client/lorawan_test.go | 5 ++--- main.go | 9 +++++++-- processor/code_processor.go | 3 +-- processor/code_processor_test.go | 18 ++++++++---------- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index 7d8dda4..d3edc9b 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -3,8 +3,6 @@ package client import ( "io" "net/http" - "path" - "time" ) // Client used to communicate to external services @@ -14,23 +12,23 @@ type Client interface { // LoraWAN used to communicate to LoRaWAN external system type LoraWAN struct { - timeout time.Duration baseURL string + client Client } -func NewLoraWAN(baseURL string, timeout time.Duration) *LoraWAN { +func NewLoraWAN(baseURL string, client Client) *LoraWAN { return &LoraWAN{ baseURL: baseURL, - timeout: timeout, + client: client, } } const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN // Send data via POST (HTTP) request -func (l *LoraWAN) DoPost(client Client, body io.Reader) (resp *http.Response, err error) { - fullUrl := path.Join(l.baseURL, endpoint) - resp, err = client.Post(fullUrl, "application/json", body) +func (l *LoraWAN) DoPost(body io.Reader) (resp *http.Response, err error) { + fullUrl := l.baseURL + endpoint + resp, err = l.client.Post(fullUrl, "application/json", body) if err != nil { return nil, err } diff --git a/client/lorawan_test.go b/client/lorawan_test.go index 57ca717..4bbd4c3 100644 --- a/client/lorawan_test.go +++ b/client/lorawan_test.go @@ -8,7 +8,6 @@ import ( "net/http" "strings" "testing" - "time" ) type MockClient struct { @@ -22,14 +21,14 @@ func TestLorawanClientHappyPath(t *testing.T) { }, } - loraWanClient := NewLoraWAN("www.example.com", time.Microsecond*30000) + loraWAN := NewLoraWAN("www.example.com", mockClient) b := new(bytes.Buffer) reqBody := map[string]string{"Deveui": "Abcde"} _ = json.NewEncoder(b).Encode(&reqBody) - resp, err := loraWanClient.DoPost(mockClient, b) + resp, err := loraWAN.DoPost(b) if err != nil { t.Errorf("err should be nil but is: %s", err.Error()) diff --git a/main.go b/main.go index c93b180..1ca4f49 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "net/http" "os" "os/signal" "syscall" @@ -39,13 +40,17 @@ func main() { godotenv.Load(".env") baseurl := os.Getenv("BASE_URL") - loraWAN := client.NewLoraWAN(baseurl, TIMEOUT) + // setup client for requests + httpClient := &http.Client{ + Timeout: time.Second * TIMEOUT, + } + + loraWAN := client.NewLoraWAN(baseurl, httpClient) // setup processor to do work codeProcessor := &processor.CodeProcessor{ CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, MaxConcurrentJobs: MAX_CONCURRENT_JOBS, - BaseUrl: baseurl, LoraWAN: *loraWAN, Device: make(chan device.Device), } diff --git a/processor/code_processor.go b/processor/code_processor.go index 47439a6..b20a0d5 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -14,7 +14,6 @@ import ( type CodeProcessor struct { CodeRegistrationLimit int MaxConcurrentJobs int - BaseUrl string LoraWAN client.LoraWAN Device chan device.Device } @@ -52,7 +51,7 @@ func registerDevice(loraWAN client.LoraWAN) (bool, *device.Device) { log.Print(err) } - resp, err := loraWAN.DoPost(&http.Client{}, b) + resp, err := loraWAN.DoPost(b) if err != nil { log.Fatal(err) diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 2543810..01fec15 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -6,17 +6,16 @@ import ( "io" "net/http" "testing" - "time" "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/device" ) type MockClient struct { - DoPost func(body io.Reader) (resp *http.Response, err error) + DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) } -func (m *MockClient) Post(body io.Reader) (resp *http.Response, err error) { +func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(nil)), @@ -30,18 +29,17 @@ const ( ) func TestCanProcessCodes(t *testing.T) { - // client := &MockClient{ - // DoPost: func(body io.Reader) (resp *http.Response, err error) { - // return &http.Response{}, nil - // }, - // } + mockClient := &MockClient{ + DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + return &http.Response{}, nil + }, + } - loraWAN := client.NewLoraWAN("http://www.mock-url.com", time.Microsecond*30000) + loraWAN := client.NewLoraWAN("www.example.com", mockClient) codeProcessor := &CodeProcessor{ CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, MaxConcurrentJobs: MAX_CONCURRENT_JOBS, - BaseUrl: "http://www.mock-url.com", Device: make(chan device.Device), LoraWAN: *loraWAN, } From 0aef7d2ba966769de59053fb811318f5716ef591 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Mon, 8 May 2023 15:40:19 +0100 Subject: [PATCH 63/83] pass context --- client/lorawan.go | 19 ++++++++++++++++--- client/lorawan_test.go | 15 +++++++++++---- processor/code_processor.go | 6 +++--- processor/code_processor_test.go | 20 ++++++++++---------- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index d3edc9b..48dfc6d 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -1,13 +1,15 @@ package client import ( + "context" "io" + "log" "net/http" ) // Client used to communicate to external services type Client interface { - Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) + Do(*http.Request) (resp *http.Response, err error) } // LoraWAN used to communicate to LoRaWAN external system @@ -26,9 +28,20 @@ func NewLoraWAN(baseURL string, client Client) *LoraWAN { const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN // Send data via POST (HTTP) request -func (l *LoraWAN) DoPost(body io.Reader) (resp *http.Response, err error) { +func (l *LoraWAN) DoPost(body io.Reader, ctx context.Context) (resp *http.Response, err error) { fullUrl := l.baseURL + endpoint - resp, err = l.client.Post(fullUrl, "application/json", body) + req, err := http.NewRequest("POST", fullUrl, body) + if err != nil { + log.Fatalf("%v", err) + } + + req = req.WithContext(ctx) + + resp, err = l.client.Do(req) + if err != nil { + log.Fatalf("%v", err) + } + if err != nil { return nil, err } diff --git a/client/lorawan_test.go b/client/lorawan_test.go index 4bbd4c3..7975105 100644 --- a/client/lorawan_test.go +++ b/client/lorawan_test.go @@ -2,6 +2,7 @@ package client import ( "bytes" + "context" "encoding/json" "io" @@ -11,12 +12,12 @@ import ( ) type MockClient struct { - DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) + DoFunc func(*http.Request) (resp *http.Response, err error) } func TestLorawanClientHappyPath(t *testing.T) { mockClient := &MockClient{ - DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + DoFunc: func(*http.Request) (resp *http.Response, err error) { return &http.Response{}, nil }, } @@ -28,7 +29,13 @@ func TestLorawanClientHappyPath(t *testing.T) { _ = json.NewEncoder(b).Encode(&reqBody) - resp, err := loraWAN.DoPost(b) + ctx, cancel := context.WithCancel(context.Background()) + + if cancel == nil { + t.Errorf("cancel should not be nil but is: %v", cancel) + } + + resp, err := loraWAN.DoPost(b, ctx) if err != nil { t.Errorf("err should be nil but is: %s", err.Error()) @@ -47,7 +54,7 @@ func TestLorawanClientHappyPath(t *testing.T) { } } -func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { +func (m *MockClient) Do(*http.Request) (resp *http.Response, err error) { b := new(bytes.Buffer) reqBody := true diff --git a/processor/code_processor.go b/processor/code_processor.go index b20a0d5..d2bc9e1 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -32,7 +32,7 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { case <-ctx.Done(): return ctx.Err() case <-work: - saved, registeredDevice := registerDevice(cp.LoraWAN) + saved, registeredDevice := registerDevice(cp.LoraWAN, ctx) if saved { cp.Device <- *registeredDevice } @@ -40,7 +40,7 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { } } -func registerDevice(loraWAN client.LoraWAN) (bool, *device.Device) { +func registerDevice(loraWAN client.LoraWAN, ctx context.Context) (bool, *device.Device) { device := device.NewDevice() b := new(bytes.Buffer) @@ -51,7 +51,7 @@ func registerDevice(loraWAN client.LoraWAN) (bool, *device.Device) { log.Print(err) } - resp, err := loraWAN.DoPost(b) + resp, err := loraWAN.DoPost(b, ctx) if err != nil { log.Fatal(err) diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 01fec15..9309699 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -12,15 +12,7 @@ import ( ) type MockClient struct { - DoPost func(url string, contentType string, body io.Reader) (resp *http.Response, err error) -} - -func (m *MockClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(nil)), - Status: "200 OK"}, - nil + DoFunc func(*http.Request) (resp *http.Response, err error) } const ( @@ -30,7 +22,7 @@ const ( func TestCanProcessCodes(t *testing.T) { mockClient := &MockClient{ - DoPost: func(url string, contentType string, body io.Reader) (resp *http.Response, err error) { + DoFunc: func(*http.Request) (resp *http.Response, err error) { return &http.Response{}, nil }, } @@ -89,3 +81,11 @@ func TestCanProcessCodes(t *testing.T) { } } } + +func (m *MockClient) Do(*http.Request) (resp *http.Response, err error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(nil)), + Status: "200 OK"}, + nil +} From f8bcd836ded5f9701d3ab3c80686d9be0b5e76f4 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 10:34:45 +0100 Subject: [PATCH 64/83] ignore Goland folder --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 807438d..f04ddec 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ # vendor/ # Ignore environment files -.env \ No newline at end of file +.env +.idea/ \ No newline at end of file From f583b64a67d55efddf6c05f66c0ce5ff4d78617e Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 10:34:59 +0100 Subject: [PATCH 65/83] Corrections based on Goland feedback --- client/lorawan.go | 4 ++-- device/device.go | 12 ++++++------ main.go | 16 ++++++++-------- processor/code_processor.go | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index 48dfc6d..434f52a 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -27,7 +27,7 @@ func NewLoraWAN(baseURL string, client Client) *LoraWAN { const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN -// Send data via POST (HTTP) request +// DoPost sends data via POST (HTTP) request func (l *LoraWAN) DoPost(body io.Reader, ctx context.Context) (resp *http.Response, err error) { fullUrl := l.baseURL + endpoint req, err := http.NewRequest("POST", fullUrl, body) @@ -39,7 +39,7 @@ func (l *LoraWAN) DoPost(body io.Reader, ctx context.Context) (resp *http.Respon resp, err = l.client.Do(req) if err != nil { - log.Fatalf("%v", err) + log.Printf("%v", err) } if err != nil { diff --git a/device/device.go b/device/device.go index c1fb085..68cfbcc 100644 --- a/device/device.go +++ b/device/device.go @@ -7,8 +7,8 @@ import ( ) const ( - ALLOWED_CHARS = "ABCDEF0123456789" // accepted chars used to make up DevEUI - DEV_EUI_LENGTH = 16 // valid DevEUI is string of length 16 + AllowedChars = "ABCDEF0123456789" // accepted chars used to make up DevEUI + DevEuiLength = 16 // valid DevEUI is string of length 16 ) type Device struct { @@ -16,7 +16,7 @@ type Device struct { Code string } -// Build new device with DevEUI identifier and code values. +// NewDevice Build a new device with DevEUI identifier and code values. // // # Example // @@ -39,14 +39,14 @@ func NewDevice() *Device { // // 1CEB0080F074F750 func generateHexString() (string, error) { - max := big.NewInt(int64(len(ALLOWED_CHARS))) - b := make([]byte, DEV_EUI_LENGTH) + max := big.NewInt(int64(len(AllowedChars))) + b := make([]byte, DevEuiLength) for i := range b { n, err := rand.Int(rand.Reader, max) if err != nil { return "", err } - b[i] = ALLOWED_CHARS[n.Int64()] + b[i] = AllowedChars[n.Int64()] } return string(b), nil } diff --git a/main.go b/main.go index 1ca4f49..ccd0b30 100644 --- a/main.go +++ b/main.go @@ -17,9 +17,9 @@ import ( ) const ( - MAX_CONCURRENT_JOBS = /* Buffer limit for channel */ 10 - CODE_REGISTRATION_LIMIT = /* Maximum number of devices that will be registered */ 100 - TIMEOUT = /* Milliseconds */ 30000 + MaxConcurrentJobs = /* Buffer limit for channel */ 10 + CodeRegistrationLimit = /* Maximum number of devices that will be registered */ 100 + TIMEOUT = /* Milliseconds */ 30000 ) /* @@ -49,8 +49,8 @@ func main() { // setup processor to do work codeProcessor := &processor.CodeProcessor{ - CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, - MaxConcurrentJobs: MAX_CONCURRENT_JOBS, + CodeRegistrationLimit: CodeRegistrationLimit, + MaxConcurrentJobs: MaxConcurrentJobs, LoraWAN: *loraWAN, Device: make(chan device.Device), } @@ -58,7 +58,7 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - work := make(chan struct{}, MAX_CONCURRENT_JOBS) + work := make(chan struct{}, MaxConcurrentJobs) listener := make(chan os.Signal, 1) // goroutine to listen for syscall.SIGINT @@ -81,7 +81,7 @@ func main() { }() // Spawn workers - for job := 0; job < MAX_CONCURRENT_JOBS; job++ { + for job := 0; job < MaxConcurrentJobs; job++ { go codeProcessor.Worker(ctx, work) } @@ -90,7 +90,7 @@ func main() { for d := range codeProcessor.Device { fmt.Printf("device: %d has identifier: %s and code: %s\n", count+1, d.Identifier, d.Code) count += 1 - if count == CODE_REGISTRATION_LIMIT { + if count == CodeRegistrationLimit { break } } diff --git a/processor/code_processor.go b/processor/code_processor.go index d2bc9e1..da677ef 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -19,7 +19,7 @@ type CodeProcessor struct { } // Worker attempts to register a valid DevEUI via external LoRaWAN API. -// If successfull, a RegisterDevice struct with it's Identifier and Code will be sent to the work channel. +// If successful, a RegisterDevice struct with it's Identifier and Code will be sent to the work channel. // // # Example // From cf8466e340635a2441987c51010bd658f7ef0a1a Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:05:28 +0100 Subject: [PATCH 66/83] Return error all the way up to main.go --- processor/code_processor.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/processor/code_processor.go b/processor/code_processor.go index da677ef..57a422b 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/json" - "log" + "errors" "net/http" "github.com/NickGowdy/deveui-cli/client" @@ -19,7 +19,7 @@ type CodeProcessor struct { } // Worker attempts to register a valid DevEUI via external LoRaWAN API. -// If successful, a RegisterDevice struct with it's Identifier and Code will be sent to the work channel. +// If successful, a RegisterDevice struct with its Identifier and Code will be sent to the work channel. // // # Example // @@ -32,15 +32,17 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { case <-ctx.Done(): return ctx.Err() case <-work: - saved, registeredDevice := registerDevice(cp.LoraWAN, ctx) - if saved { + registeredDevice, err := registerDevice(cp.LoraWAN, ctx) + if err == nil { cp.Device <- *registeredDevice + } else { + return err } } } } -func registerDevice(loraWAN client.LoraWAN, ctx context.Context) (bool, *device.Device) { +func registerDevice(loraWAN client.LoraWAN, ctx context.Context) (*device.Device, error) { device := device.NewDevice() b := new(bytes.Buffer) @@ -48,20 +50,19 @@ func registerDevice(loraWAN client.LoraWAN, ctx context.Context) (bool, *device. err := json.NewEncoder(b).Encode(&reqBody) if err != nil { - log.Print(err) + return nil, err } resp, err := loraWAN.DoPost(b, ctx) - if err != nil { - log.Fatal(err) + return nil, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { - return true, device + return device, nil } else { - return false, nil + return nil, errors.New(resp.Status) } } From bf7a72acca4aab17181642de695a50fa87381822 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:05:43 +0100 Subject: [PATCH 67/83] Use env variables for all config --- main.go | 55 +++++++++++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/main.go b/main.go index ccd0b30..693974d 100644 --- a/main.go +++ b/main.go @@ -3,11 +3,9 @@ package main import ( "context" "fmt" - "log" "net/http" "os" - "os/signal" - "syscall" + "strconv" "time" "github.com/NickGowdy/deveui-cli/client" @@ -16,12 +14,6 @@ import ( "github.com/joho/godotenv" ) -const ( - MaxConcurrentJobs = /* Buffer limit for channel */ 10 - CodeRegistrationLimit = /* Maximum number of devices that will be registered */ 100 - TIMEOUT = /* Milliseconds */ 30000 -) - /* deveui-cli is a Go CLI program. It is used for concurrently generating unique 16-character (hex) identifier called a DevEUI. @@ -37,20 +29,40 @@ Once this program starts, it will listen to syscall.SIGTERM, syscall.SIGINT via This is to handle any unexpected terminations of the program and to resume processing DevEUIs. */ func main() { + if err := godotenv.Load(".env"); err != nil { + panic("error loading.env file") + } godotenv.Load(".env") baseurl := os.Getenv("BASE_URL") + maxConcurrentJobs, err := strconv.Atoi(os.Getenv("MAX_CONCURRENT_JOBS")) + if err != nil { + panic("error parsing MAX_CONCURRENT_JOBS to int") + } + + codeRegistrationLimit, err := strconv.Atoi(os.Getenv("CODE_REGISTRATION_LIMIT")) + if err != nil { + panic("error parsing CODE_REGISTRATION_LIMIT to int") + } + + timeout, err := strconv.Atoi(os.Getenv("TIMEOUT")) + if err != nil { + panic("error parsing TIMEOUT to int") + } + + seconds := time.Second * time.Duration(timeout) + // setup client for requests httpClient := &http.Client{ - Timeout: time.Second * TIMEOUT, + Timeout: seconds, } loraWAN := client.NewLoraWAN(baseurl, httpClient) // setup processor to do work codeProcessor := &processor.CodeProcessor{ - CodeRegistrationLimit: CodeRegistrationLimit, - MaxConcurrentJobs: MaxConcurrentJobs, + CodeRegistrationLimit: codeRegistrationLimit, + MaxConcurrentJobs: maxConcurrentJobs, LoraWAN: *loraWAN, Device: make(chan device.Device), } @@ -58,20 +70,7 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - work := make(chan struct{}, MaxConcurrentJobs) - listener := make(chan os.Signal, 1) - - // goroutine to listen for syscall.SIGINT - go func() { - signal.Notify(listener, syscall.SIGINT) - go func() { - for { - time.Sleep(1000) - } - }() - sig := <-listener - log.Printf("Caught signal %v", sig) - }() + work := make(chan struct{}, maxConcurrentJobs) // Fill work buffer so we can start processing work go func() { @@ -81,7 +80,7 @@ func main() { }() // Spawn workers - for job := 0; job < MaxConcurrentJobs; job++ { + for job := 0; job < maxConcurrentJobs; job++ { go codeProcessor.Worker(ctx, work) } @@ -90,7 +89,7 @@ func main() { for d := range codeProcessor.Device { fmt.Printf("device: %d has identifier: %s and code: %s\n", count+1, d.Identifier, d.Code) count += 1 - if count == CodeRegistrationLimit { + if count == codeRegistrationLimit { break } } From 0815e22d080bdecd143aeb82d00fea0c426383be Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:14:54 +0100 Subject: [PATCH 68/83] encapsulate device values and create public funcs to return what we need --- device/device.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/device/device.go b/device/device.go index 68cfbcc..fe5b0d6 100644 --- a/device/device.go +++ b/device/device.go @@ -2,6 +2,7 @@ package device import ( "crypto/rand" + "fmt" "log" "math/big" ) @@ -12,8 +13,8 @@ const ( ) type Device struct { - Identifier string - Code string + identifier string + code string } // NewDevice Build a new device with DevEUI identifier and code values. @@ -28,11 +29,19 @@ func NewDevice() *Device { } return &Device{ - Identifier: hex, - Code: hex[len(hex)-5:], + identifier: hex, + code: hex[len(hex)-5:], } } +func (d Device) Get() string { + return d.identifier +} + +func (d Device) Print(number int) { + fmt.Printf("device: %d has identifier: %s and code: %s\n", number+1, d.identifier, d.code) +} + // Generate valid DevEUI identifier value. // // # Example From 0584c5f226663fab4d67dd7dbfc91c6402bc7019 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:15:15 +0100 Subject: [PATCH 69/83] use device public values --- main.go | 3 +-- processor/code_processor.go | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 693974d..ce9a5d8 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "net/http" "os" "strconv" @@ -87,7 +86,7 @@ func main() { // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. count := 0 for d := range codeProcessor.Device { - fmt.Printf("device: %d has identifier: %s and code: %s\n", count+1, d.Identifier, d.Code) + d.Print(count) count += 1 if count == codeRegistrationLimit { break diff --git a/processor/code_processor.go b/processor/code_processor.go index 57a422b..2f32655 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -44,9 +44,10 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { func registerDevice(loraWAN client.LoraWAN, ctx context.Context) (*device.Device, error) { device := device.NewDevice() + identifier := device.Get() b := new(bytes.Buffer) - reqBody := map[string]string{"Deveui": device.Identifier} + reqBody := map[string]string{"Deveui": identifier} err := json.NewEncoder(b).Encode(&reqBody) if err != nil { From 827623628d88b9067b36e76edc758d437b1a03c5 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:20:37 +0100 Subject: [PATCH 70/83] tests now passing again --- device/device.go | 6 +++++- device/device_test.go | 31 +++++++++++++++++-------------- processor/code_processor.go | 2 +- processor/code_processor_test.go | 19 +++++++++++-------- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/device/device.go b/device/device.go index fe5b0d6..3e54043 100644 --- a/device/device.go +++ b/device/device.go @@ -34,10 +34,14 @@ func NewDevice() *Device { } } -func (d Device) Get() string { +func (d Device) GetIdentifier() string { return d.identifier } +func (d Device) GetCode() string { + return d.code +} + func (d Device) Print(number int) { fmt.Printf("device: %d has identifier: %s and code: %s\n", number+1, d.identifier, d.code) } diff --git a/device/device_test.go b/device/device_test.go index 700a890..46fa331 100644 --- a/device/device_test.go +++ b/device/device_test.go @@ -12,17 +12,20 @@ func TestCanGenerateValidCode(t *testing.T) { t.Errorf("deivce should not be nil, but is %s", device) } - if len(device.Code) != 5 { - t.Errorf("code should be 5 characters long, but is %d", len(device.Code)) + identifier := device.GetIdentifier() + code := device.GetCode() + + if len(code) != 5 { + t.Errorf("code should be 5 characters long, but is %d", len(code)) } - if device.Identifier[len(device.Identifier)-5:] != device.Code { - t.Errorf("code should be last 5 characters of identifier, but is %s", device.Code) + if identifier[len(identifier)-5:] != code { + t.Errorf("code should be last 5 characters of identifier, but is %s", code) } hasChar := false for _, char := range allowedChars { - if char == string(device.Code[0]) { + if char == string(code[0]) { hasChar = true } else if hasChar { break @@ -30,12 +33,12 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("first char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[0])) + t.Errorf("first char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[0])) } hasChar = false for _, char := range allowedChars { - if char == string(device.Code[1]) { + if char == string(code[1]) { hasChar = true } else if hasChar { break @@ -43,12 +46,12 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("second char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[1])) + t.Errorf("second char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[1])) } hasChar = false for _, char := range allowedChars { - if char == string(device.Code[2]) { + if char == string(code[2]) { hasChar = true } else if hasChar { break @@ -56,12 +59,12 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("third char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[2])) + t.Errorf("third char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[2])) } hasChar = false for _, char := range allowedChars { - if char == string(device.Code[3]) { + if char == string(code[3]) { hasChar = true } else if hasChar { break @@ -69,12 +72,12 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("fourth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[3])) + t.Errorf("fourth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[3])) } hasChar = false for _, char := range allowedChars { - if char == string(device.Code[4]) { + if char == string(code[4]) { hasChar = true } else if hasChar { break @@ -82,6 +85,6 @@ func TestCanGenerateValidCode(t *testing.T) { } if hasChar == false { - t.Errorf("fifth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(device.Code[4])) + t.Errorf("fifth char should be A, B, C, D, E, F, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9: but is: %s", string(code[4])) } } diff --git a/processor/code_processor.go b/processor/code_processor.go index 2f32655..e5b6802 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -44,7 +44,7 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { func registerDevice(loraWAN client.LoraWAN, ctx context.Context) (*device.Device, error) { device := device.NewDevice() - identifier := device.Get() + identifier := device.GetIdentifier() b := new(bytes.Buffer) reqBody := map[string]string{"Deveui": identifier} diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 9309699..2a2b3c1 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -55,24 +55,27 @@ func TestCanProcessCodes(t *testing.T) { n := 0 for d := range codeProcessor.Device { - if d.Code == "" { + identifier := d.GetIdentifier() + code := d.GetCode() + + if code == "" { t.Error("code should not be nil") } - if d.Identifier == "" { + if identifier == "" { t.Error("identifier should not be nil") } - if d.Identifier[len(d.Identifier)-5:] != d.Code { - t.Errorf("code should be last 5 characters of identifier, but is %s", d.Code) + if identifier[len(identifier)-5:] != code { + t.Errorf("code should be last 5 characters of identifier, but is %s", code) } - if len(d.Identifier) != 16 { - t.Errorf("identifier should be exactly 16 characters, but is %d", len(d.Identifier)) + if len(identifier) != 16 { + t.Errorf("identifier should be exactly 16 characters, but is %d", len(identifier)) } - if len(d.Code) != 5 { - t.Errorf("code should be exactly 5 characters, but is %d", len(d.Code)) + if len(code) != 5 { + t.Errorf("code should be exactly 5 characters, but is %d", len(code)) } n += 1 From 3fd84f05e49c9e164c1823e20a6d18ca1bbfbd71 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:23:52 +0100 Subject: [PATCH 71/83] return nil, err here as well --- client/lorawan.go | 5 ++--- main.go | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index 434f52a..d023830 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -3,7 +3,6 @@ package client import ( "context" "io" - "log" "net/http" ) @@ -32,14 +31,14 @@ func (l *LoraWAN) DoPost(body io.Reader, ctx context.Context) (resp *http.Respon fullUrl := l.baseURL + endpoint req, err := http.NewRequest("POST", fullUrl, body) if err != nil { - log.Fatalf("%v", err) + return nil, err } req = req.WithContext(ctx) resp, err = l.client.Do(req) if err != nil { - log.Printf("%v", err) + return nil, err } if err != nil { diff --git a/main.go b/main.go index ce9a5d8..7ebeac8 100644 --- a/main.go +++ b/main.go @@ -85,9 +85,9 @@ func main() { // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. count := 0 - for d := range codeProcessor.Device { - d.Print(count) - count += 1 + for device := range codeProcessor.Device { + device.Print(count) + count++ if count == codeRegistrationLimit { break } From 49f4420d1c40ceaa1fecaa709bdfb4517962ec19 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:29:22 +0100 Subject: [PATCH 72/83] use request with context instead --- client/lorawan.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index d023830..6ad5eac 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -29,13 +29,11 @@ const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via L // DoPost sends data via POST (HTTP) request func (l *LoraWAN) DoPost(body io.Reader, ctx context.Context) (resp *http.Response, err error) { fullUrl := l.baseURL + endpoint - req, err := http.NewRequest("POST", fullUrl, body) + req, err := http.NewRequestWithContext(ctx, "POST", fullUrl, body) if err != nil { return nil, err } - req = req.WithContext(ctx) - resp, err = l.client.Do(req) if err != nil { return nil, err From 2565ebc57f5a07f1eca10244c0a01cf6e42b8b1d Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:29:33 +0100 Subject: [PATCH 73/83] small refactor --- main.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/main.go b/main.go index 7ebeac8..d9ee26f 100644 --- a/main.go +++ b/main.go @@ -49,11 +49,9 @@ func main() { panic("error parsing TIMEOUT to int") } - seconds := time.Second * time.Duration(timeout) - // setup client for requests httpClient := &http.Client{ - Timeout: seconds, + Timeout: time.Second * time.Duration(timeout), } loraWAN := client.NewLoraWAN(baseurl, httpClient) From e869ad74b176d22e7cf55a0e518d578efcf7fc72 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:35:22 +0100 Subject: [PATCH 74/83] use codeRegistrationLimit instead --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index d9ee26f..3a0b00a 100644 --- a/main.go +++ b/main.go @@ -77,7 +77,7 @@ func main() { }() // Spawn workers - for job := 0; job < maxConcurrentJobs; job++ { + for job := 0; job < codeRegistrationLimit; job++ { go codeProcessor.Worker(ctx, work) } From 179181b64d13373dd07ec859eedd2af23c77b671 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 11:47:11 +0100 Subject: [PATCH 75/83] more tests --- client/lorawan_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/client/lorawan_test.go b/client/lorawan_test.go index 7975105..c2a2d1b 100644 --- a/client/lorawan_test.go +++ b/client/lorawan_test.go @@ -5,6 +5,8 @@ import ( "context" "encoding/json" "io" + "reflect" + "time" "net/http" "strings" @@ -54,6 +56,41 @@ func TestLorawanClientHappyPath(t *testing.T) { } } +func TestNewLoraWanClient(t *testing.T) { + client := &http.Client{ + Timeout: 30 * time.Second, + } + t.Parallel() + type args struct { + timeout time.Duration + } + tests := []struct { + name string + args args + want *LoraWAN + }{ + { + name: "create-new-lorawan-client", + args: args{ + timeout: 30, + }, + want: &LoraWAN{ + baseURL: "https://www.example.com", + client: client, + }, + }, + } + for _, tt := range tests { + tt := tt // it is important to capture range variable + t.Run(tt.name, func(t *testing.T) { + t.Parallel() // this makes sure that all cases from the table here are executed in parallel + if got := NewLoraWAN("https://www.example.com", client); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewLoraWanClient() = %v, want %v", got, tt.want) + } + }) + } +} + func (m *MockClient) Do(*http.Request) (resp *http.Response, err error) { b := new(bytes.Buffer) reqBody := true From 16048cd31645e4ef4cfce1398b899bcd5f3fedbf Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 14:16:30 +0100 Subject: [PATCH 76/83] close channel once done --- main.go | 13 +++++++------ processor/code_processor.go | 6 +++--- processor/code_processor_test.go | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 3a0b00a..7c8b9da 100644 --- a/main.go +++ b/main.go @@ -58,35 +58,36 @@ func main() { // setup processor to do work codeProcessor := &processor.CodeProcessor{ - CodeRegistrationLimit: codeRegistrationLimit, + CodeRegistrationLimit: 1, MaxConcurrentJobs: maxConcurrentJobs, LoraWAN: *loraWAN, - Device: make(chan device.Device), + DeviceCh: make(chan device.Device), } ctx, cancel := context.WithCancel(context.Background()) defer cancel() - work := make(chan struct{}, maxConcurrentJobs) + workCh := make(chan device.Device, maxConcurrentJobs) // Fill work buffer so we can start processing work go func() { for { - work <- struct{}{} + workCh <- device.Device{} } }() // Spawn workers for job := 0; job < codeRegistrationLimit; job++ { - go codeProcessor.Worker(ctx, work) + go codeProcessor.Worker(ctx, workCh) } // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. count := 0 - for device := range codeProcessor.Device { + for device := range codeProcessor.DeviceCh { device.Print(count) count++ if count == codeRegistrationLimit { + close(workCh) break } } diff --git a/processor/code_processor.go b/processor/code_processor.go index e5b6802..255e732 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -15,7 +15,7 @@ type CodeProcessor struct { CodeRegistrationLimit int MaxConcurrentJobs int LoraWAN client.LoraWAN - Device chan device.Device + DeviceCh chan device.Device } // Worker attempts to register a valid DevEUI via external LoRaWAN API. @@ -26,7 +26,7 @@ type CodeProcessor struct { // Identifier: 1CEB0080F074F750, Code: 4F750 // // When an unexpected error occurs, return ctx.Err instead. -func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { +func (cp *CodeProcessor) Worker(ctx context.Context, work chan device.Device) error { for { select { case <-ctx.Done(): @@ -34,7 +34,7 @@ func (cp *CodeProcessor) Worker(ctx context.Context, work chan struct{}) error { case <-work: registeredDevice, err := registerDevice(cp.LoraWAN, ctx) if err == nil { - cp.Device <- *registeredDevice + cp.DeviceCh <- *registeredDevice } else { return err } diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index 2a2b3c1..c6a0519 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -32,7 +32,7 @@ func TestCanProcessCodes(t *testing.T) { codeProcessor := &CodeProcessor{ CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, MaxConcurrentJobs: MAX_CONCURRENT_JOBS, - Device: make(chan device.Device), + DeviceCh: make(chan device.Device), LoraWAN: *loraWAN, } @@ -53,7 +53,7 @@ func TestCanProcessCodes(t *testing.T) { } n := 0 - for d := range codeProcessor.Device { + for d := range codeProcessor.DeviceCh { identifier := d.GetIdentifier() code := d.GetCode() From 6fee7cdd99de778030ffb17540255bd996bca812 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 14:19:43 +0100 Subject: [PATCH 77/83] use same buffer --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 7c8b9da..a46a32d 100644 --- a/main.go +++ b/main.go @@ -61,7 +61,7 @@ func main() { CodeRegistrationLimit: 1, MaxConcurrentJobs: maxConcurrentJobs, LoraWAN: *loraWAN, - DeviceCh: make(chan device.Device), + DeviceCh: make(chan device.Device, maxConcurrentJobs), } ctx, cancel := context.WithCancel(context.Background()) From b638ffa81590b7bbb7e5d44df4d36ca6d3880fb7 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 14:51:32 +0100 Subject: [PATCH 78/83] trying to use done pattern --- main.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index a46a32d..1de3e51 100644 --- a/main.go +++ b/main.go @@ -68,13 +68,16 @@ func main() { defer cancel() workCh := make(chan device.Device, maxConcurrentJobs) + doneCh := make(chan struct{}) + count := 0 // Fill work buffer so we can start processing work - go func() { - for { + go func(count int) { + for count < 100 { workCh <- device.Device{} } - }() + + }(count) // Spawn workers for job := 0; job < codeRegistrationLimit; job++ { @@ -82,13 +85,13 @@ func main() { } // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. - count := 0 + for device := range codeProcessor.DeviceCh { device.Print(count) count++ if count == codeRegistrationLimit { - close(workCh) break } } + close(doneCh) } From a3f629925a6f73927046969ebe3106de81009171 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Fri, 12 May 2023 15:53:07 +0100 Subject: [PATCH 79/83] this calls done --- main.go | 5 +++-- processor/code_processor.go | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 1de3e51..d934653 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,7 @@ func main() { MaxConcurrentJobs: maxConcurrentJobs, LoraWAN: *loraWAN, DeviceCh: make(chan device.Device, maxConcurrentJobs), + DoneCh: make(chan struct{}), } ctx, cancel := context.WithCancel(context.Background()) @@ -76,12 +77,12 @@ func main() { for count < 100 { workCh <- device.Device{} } - + return }(count) // Spawn workers for job := 0; job < codeRegistrationLimit; job++ { - go codeProcessor.Worker(ctx, workCh) + go codeProcessor.Worker(ctx, workCh, doneCh) } // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. diff --git a/processor/code_processor.go b/processor/code_processor.go index 255e732..13189fe 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "net/http" "github.com/NickGowdy/deveui-cli/client" @@ -16,6 +17,7 @@ type CodeProcessor struct { MaxConcurrentJobs int LoraWAN client.LoraWAN DeviceCh chan device.Device + DoneCh chan struct{} } // Worker attempts to register a valid DevEUI via external LoRaWAN API. @@ -26,18 +28,21 @@ type CodeProcessor struct { // Identifier: 1CEB0080F074F750, Code: 4F750 // // When an unexpected error occurs, return ctx.Err instead. -func (cp *CodeProcessor) Worker(ctx context.Context, work chan device.Device) error { +func (cp *CodeProcessor) Worker(ctx context.Context, workCh chan device.Device, doneCh chan struct{}) error { for { select { case <-ctx.Done(): return ctx.Err() - case <-work: + case <-workCh: registeredDevice, err := registerDevice(cp.LoraWAN, ctx) if err == nil { cp.DeviceCh <- *registeredDevice } else { return err } + case <-doneCh: + fmt.Printf("Done \n") + return nil } } } From fabb4a97d0ad89fcf77b161ed63a1939b1b219d7 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sat, 13 May 2023 10:29:17 +0100 Subject: [PATCH 80/83] WIP - refactor (encapsulate implementation of registering in LoraWAN --- client/lorawan.go | 32 ++++-- client/lorawan_test.go | 179 +++++++++++++++---------------- main.go | 5 +- processor/code_processor.go | 39 +------ processor/code_processor_test.go | 151 +++++++++++++------------- 5 files changed, 187 insertions(+), 219 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index 6ad5eac..a82758e 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -1,9 +1,13 @@ package client import ( + "bytes" "context" - "io" + "encoding/json" + "errors" "net/http" + + "github.com/NickGowdy/deveui-cli/device" ) // Client used to communicate to external services @@ -26,15 +30,25 @@ func NewLoraWAN(baseURL string, client Client) *LoraWAN { const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN -// DoPost sends data via POST (HTTP) request -func (l *LoraWAN) DoPost(body io.Reader, ctx context.Context) (resp *http.Response, err error) { +// Send registers new device using LoraWAN external service +func (l *LoraWAN) Send(ctx context.Context) (*device.Device, error) { + device := device.NewDevice() + identifier := device.GetIdentifier() + b := new(bytes.Buffer) + reqBody := map[string]string{"Deveui": identifier} + + err := json.NewEncoder(b).Encode(&reqBody) + if err != nil { + return nil, err + } + fullUrl := l.baseURL + endpoint - req, err := http.NewRequestWithContext(ctx, "POST", fullUrl, body) + req, err := http.NewRequestWithContext(ctx, "POST", fullUrl, b) if err != nil { return nil, err } - resp, err = l.client.Do(req) + resp, err := l.client.Do(req) if err != nil { return nil, err } @@ -43,5 +57,11 @@ func (l *LoraWAN) DoPost(body io.Reader, ctx context.Context) (resp *http.Respon return nil, err } - return resp, nil + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + return device, nil + } else { + return nil, errors.New(resp.Status) + } } diff --git a/client/lorawan_test.go b/client/lorawan_test.go index c2a2d1b..e10ddf3 100644 --- a/client/lorawan_test.go +++ b/client/lorawan_test.go @@ -1,104 +1,95 @@ package client import ( - "bytes" - "context" - "encoding/json" - "io" - "reflect" - "time" - "net/http" - "strings" - "testing" ) type MockClient struct { DoFunc func(*http.Request) (resp *http.Response, err error) } -func TestLorawanClientHappyPath(t *testing.T) { - mockClient := &MockClient{ - DoFunc: func(*http.Request) (resp *http.Response, err error) { - return &http.Response{}, nil - }, - } - - loraWAN := NewLoraWAN("www.example.com", mockClient) - - b := new(bytes.Buffer) - reqBody := map[string]string{"Deveui": "Abcde"} - - _ = json.NewEncoder(b).Encode(&reqBody) - - ctx, cancel := context.WithCancel(context.Background()) - - if cancel == nil { - t.Errorf("cancel should not be nil but is: %v", cancel) - } - - resp, err := loraWAN.DoPost(b, ctx) - - if err != nil { - t.Errorf("err should be nil but is: %s", err.Error()) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - t.Errorf("resp should be nil but is: %d", resp.StatusCode) - } - - body, _ := io.ReadAll(resp.Body) - val := string(body) - - if strings.TrimSpace(val) != "true" { - t.Errorf("body should equal true but is: %d", body) - } -} - -func TestNewLoraWanClient(t *testing.T) { - client := &http.Client{ - Timeout: 30 * time.Second, - } - t.Parallel() - type args struct { - timeout time.Duration - } - tests := []struct { - name string - args args - want *LoraWAN - }{ - { - name: "create-new-lorawan-client", - args: args{ - timeout: 30, - }, - want: &LoraWAN{ - baseURL: "https://www.example.com", - client: client, - }, - }, - } - for _, tt := range tests { - tt := tt // it is important to capture range variable - t.Run(tt.name, func(t *testing.T) { - t.Parallel() // this makes sure that all cases from the table here are executed in parallel - if got := NewLoraWAN("https://www.example.com", client); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewLoraWanClient() = %v, want %v", got, tt.want) - } - }) - } -} - -func (m *MockClient) Do(*http.Request) (resp *http.Response, err error) { - b := new(bytes.Buffer) - reqBody := true - - _ = json.NewEncoder(b).Encode(&reqBody) - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(b), - Status: "200 OK"}, - nil -} +// func TestLorawanClientHappyPath(t *testing.T) { +// mockClient := &MockClient{ +// DoFunc: func(*http.Request) (resp *http.Response, err error) { +// return &http.Response{}, nil +// }, +// } + +// loraWAN := NewLoraWAN("www.example.com", mockClient) + +// b := new(bytes.Buffer) +// reqBody := map[string]string{"Deveui": "Abcde"} + +// _ = json.NewEncoder(b).Encode(&reqBody) + +// ctx, cancel := context.WithCancel(context.Background()) + +// if cancel == nil { +// t.Errorf("cancel should not be nil but is: %v", cancel) +// } + +// resp, err := loraWAN.DoPost(b, ctx) + +// if err != nil { +// t.Errorf("err should be nil but is: %s", err.Error()) +// } +// defer resp.Body.Close() + +// if resp.StatusCode != 200 { +// t.Errorf("resp should be nil but is: %d", resp.StatusCode) +// } + +// body, _ := io.ReadAll(resp.Body) +// val := string(body) + +// if strings.TrimSpace(val) != "true" { +// t.Errorf("body should equal true but is: %d", body) +// } +// } + +// func TestNewLoraWanClient(t *testing.T) { +// client := &http.Client{ +// Timeout: 30 * time.Second, +// } +// t.Parallel() +// type args struct { +// timeout time.Duration +// } +// tests := []struct { +// name string +// args args +// want *LoraWAN +// }{ +// { +// name: "create-new-lorawan-client", +// args: args{ +// timeout: 30, +// }, +// want: &LoraWAN{ +// baseURL: "https://www.example.com", +// client: client, +// }, +// }, +// } +// for _, tt := range tests { +// tt := tt // it is important to capture range variable +// t.Run(tt.name, func(t *testing.T) { +// t.Parallel() // this makes sure that all cases from the table here are executed in parallel +// if got := NewLoraWAN("https://www.example.com", client); !reflect.DeepEqual(got, tt.want) { +// t.Errorf("NewLoraWanClient() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func (m *MockClient) Do(*http.Request) (resp *http.Response, err error) { +// b := new(bytes.Buffer) +// reqBody := true + +// _ = json.NewEncoder(b).Encode(&reqBody) +// return &http.Response{ +// StatusCode: http.StatusOK, +// Body: io.NopCloser(b), +// Status: "200 OK"}, +// nil +// } diff --git a/main.go b/main.go index d934653..1f577ea 100644 --- a/main.go +++ b/main.go @@ -69,7 +69,6 @@ func main() { defer cancel() workCh := make(chan device.Device, maxConcurrentJobs) - doneCh := make(chan struct{}) count := 0 // Fill work buffer so we can start processing work @@ -82,11 +81,10 @@ func main() { // Spawn workers for job := 0; job < codeRegistrationLimit; job++ { - go codeProcessor.Worker(ctx, workCh, doneCh) + go codeProcessor.Worker(ctx, workCh) } // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. - for device := range codeProcessor.DeviceCh { device.Print(count) count++ @@ -94,5 +92,4 @@ func main() { break } } - close(doneCh) } diff --git a/processor/code_processor.go b/processor/code_processor.go index 13189fe..c0c9a04 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -1,12 +1,7 @@ package processor import ( - "bytes" "context" - "encoding/json" - "errors" - "fmt" - "net/http" "github.com/NickGowdy/deveui-cli/client" "github.com/NickGowdy/deveui-cli/device" @@ -28,47 +23,19 @@ type CodeProcessor struct { // Identifier: 1CEB0080F074F750, Code: 4F750 // // When an unexpected error occurs, return ctx.Err instead. -func (cp *CodeProcessor) Worker(ctx context.Context, workCh chan device.Device, doneCh chan struct{}) error { +func (cp *CodeProcessor) Worker(ctx context.Context, workCh chan device.Device) error { for { select { case <-ctx.Done(): return ctx.Err() case <-workCh: - registeredDevice, err := registerDevice(cp.LoraWAN, ctx) + registeredDevice, err := cp.LoraWAN.Send(ctx) if err == nil { cp.DeviceCh <- *registeredDevice + } else { return err } - case <-doneCh: - fmt.Printf("Done \n") - return nil } } } - -func registerDevice(loraWAN client.LoraWAN, ctx context.Context) (*device.Device, error) { - device := device.NewDevice() - identifier := device.GetIdentifier() - - b := new(bytes.Buffer) - reqBody := map[string]string{"Deveui": identifier} - - err := json.NewEncoder(b).Encode(&reqBody) - if err != nil { - return nil, err - } - - resp, err := loraWAN.DoPost(b, ctx) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - return device, nil - } else { - return nil, errors.New(resp.Status) - } -} diff --git a/processor/code_processor_test.go b/processor/code_processor_test.go index c6a0519..84334f2 100644 --- a/processor/code_processor_test.go +++ b/processor/code_processor_test.go @@ -1,14 +1,7 @@ package processor import ( - "bytes" - "context" - "io" "net/http" - "testing" - - "github.com/NickGowdy/deveui-cli/client" - "github.com/NickGowdy/deveui-cli/device" ) type MockClient struct { @@ -20,75 +13,75 @@ const ( CODE_REGISTRATION_LIMIT = 10 ) -func TestCanProcessCodes(t *testing.T) { - mockClient := &MockClient{ - DoFunc: func(*http.Request) (resp *http.Response, err error) { - return &http.Response{}, nil - }, - } - - loraWAN := client.NewLoraWAN("www.example.com", mockClient) - - codeProcessor := &CodeProcessor{ - CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, - MaxConcurrentJobs: MAX_CONCURRENT_JOBS, - DeviceCh: make(chan device.Device), - LoraWAN: *loraWAN, - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - work := make(chan struct{}, MAX_CONCURRENT_JOBS) - - go func() { - for { - work <- struct{}{} - } - }() - - // Spawn workers - for j := 0; j < MAX_CONCURRENT_JOBS; j++ { - go codeProcessor.Worker(ctx, work) - } - - n := 0 - for d := range codeProcessor.DeviceCh { - - identifier := d.GetIdentifier() - code := d.GetCode() - - if code == "" { - t.Error("code should not be nil") - } - - if identifier == "" { - t.Error("identifier should not be nil") - } - - if identifier[len(identifier)-5:] != code { - t.Errorf("code should be last 5 characters of identifier, but is %s", code) - } - - if len(identifier) != 16 { - t.Errorf("identifier should be exactly 16 characters, but is %d", len(identifier)) - } - - if len(code) != 5 { - t.Errorf("code should be exactly 5 characters, but is %d", len(code)) - } - - n += 1 - if n == CODE_REGISTRATION_LIMIT { - break - } - } -} - -func (m *MockClient) Do(*http.Request) (resp *http.Response, err error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader(nil)), - Status: "200 OK"}, - nil -} +// func TestCanProcessCodes(t *testing.T) { +// mockClient := &MockClient{ +// DoFunc: func(*http.Request) (resp *http.Response, err error) { +// return &http.Response{}, nil +// }, +// } + +// loraWAN := client.NewLoraWAN("www.example.com", mockClient) + +// codeProcessor := &CodeProcessor{ +// CodeRegistrationLimit: CODE_REGISTRATION_LIMIT, +// MaxConcurrentJobs: MAX_CONCURRENT_JOBS, +// DeviceCh: make(chan device.Device), +// LoraWAN: *loraWAN, +// } + +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() + +// work := make(chan struct{}, MAX_CONCURRENT_JOBS) + +// go func() { +// for { +// work <- struct{}{} +// } +// }() + +// // Spawn workers +// for j := 0; j < MAX_CONCURRENT_JOBS; j++ { +// go codeProcessor.Worker(ctx, work) +// } + +// n := 0 +// for d := range codeProcessor.DeviceCh { + +// identifier := d.GetIdentifier() +// code := d.GetCode() + +// if code == "" { +// t.Error("code should not be nil") +// } + +// if identifier == "" { +// t.Error("identifier should not be nil") +// } + +// if identifier[len(identifier)-5:] != code { +// t.Errorf("code should be last 5 characters of identifier, but is %s", code) +// } + +// if len(identifier) != 16 { +// t.Errorf("identifier should be exactly 16 characters, but is %d", len(identifier)) +// } + +// if len(code) != 5 { +// t.Errorf("code should be exactly 5 characters, but is %d", len(code)) +// } + +// n += 1 +// if n == CODE_REGISTRATION_LIMIT { +// break +// } +// } +// } + +// func (m *MockClient) Do(*http.Request) (resp *http.Response, err error) { +// return &http.Response{ +// StatusCode: http.StatusOK, +// Body: io.NopCloser(bytes.NewReader(nil)), +// Status: "200 OK"}, +// nil +// } From 0843818d487f64d141d2d832b492df58605e03e5 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sat, 13 May 2023 13:44:10 +0100 Subject: [PATCH 81/83] refactor of work processor --- client/lorawan.go | 4 ++-- device/device.go | 4 ++-- main.go | 33 +++------------------------- processor/code_processor.go | 44 +++++++++++++++++++++---------------- 4 files changed, 32 insertions(+), 53 deletions(-) diff --git a/client/lorawan.go b/client/lorawan.go index a82758e..d832e17 100644 --- a/client/lorawan.go +++ b/client/lorawan.go @@ -30,8 +30,8 @@ func NewLoraWAN(baseURL string, client Client) *LoraWAN { const endpoint = "/sensor-onboarding-sample" // endpoint for saving DevEUI via LoRaWAN -// Send registers new device using LoraWAN external service -func (l *LoraWAN) Send(ctx context.Context) (*device.Device, error) { +// RegisterDevice registers new device using LoraWAN external service +func (l *LoraWAN) RegisterDevice(ctx context.Context) (*device.Device, error) { device := device.NewDevice() identifier := device.GetIdentifier() b := new(bytes.Buffer) diff --git a/device/device.go b/device/device.go index 3e54043..6e94089 100644 --- a/device/device.go +++ b/device/device.go @@ -42,8 +42,8 @@ func (d Device) GetCode() string { return d.code } -func (d Device) Print(number int) { - fmt.Printf("device: %d has identifier: %s and code: %s\n", number+1, d.identifier, d.code) +func (d Device) Print() { + fmt.Printf("device has identifier: %s and code: %s\n", d.identifier, d.code) } // Generate valid DevEUI identifier value. diff --git a/main.go b/main.go index 1f577ea..619f3c5 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( "time" "github.com/NickGowdy/deveui-cli/client" - "github.com/NickGowdy/deveui-cli/device" "github.com/NickGowdy/deveui-cli/processor" "github.com/joho/godotenv" ) @@ -31,7 +30,7 @@ func main() { if err := godotenv.Load(".env"); err != nil { panic("error loading.env file") } - godotenv.Load(".env") + baseurl := os.Getenv("BASE_URL") maxConcurrentJobs, err := strconv.Atoi(os.Getenv("MAX_CONCURRENT_JOBS")) @@ -58,38 +57,12 @@ func main() { // setup processor to do work codeProcessor := &processor.CodeProcessor{ - CodeRegistrationLimit: 1, + CodeRegistrationLimit: codeRegistrationLimit, MaxConcurrentJobs: maxConcurrentJobs, LoraWAN: *loraWAN, - DeviceCh: make(chan device.Device, maxConcurrentJobs), - DoneCh: make(chan struct{}), } ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - workCh := make(chan device.Device, maxConcurrentJobs) - count := 0 - - // Fill work buffer so we can start processing work - go func(count int) { - for count < 100 { - workCh <- device.Device{} - } - return - }(count) - - // Spawn workers - for job := 0; job < codeRegistrationLimit; job++ { - go codeProcessor.Worker(ctx, workCh) - } - // stdout any registered devices and increment until CODE_REGISTRATION_LIMIT is reached. - for device := range codeProcessor.DeviceCh { - device.Print(count) - count++ - if count == codeRegistrationLimit { - break - } - } + codeProcessor.Start(ctx, cancel) } diff --git a/processor/code_processor.go b/processor/code_processor.go index c0c9a04..5812f6e 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -2,40 +2,46 @@ package processor import ( "context" + "fmt" "github.com/NickGowdy/deveui-cli/client" - "github.com/NickGowdy/deveui-cli/device" ) type CodeProcessor struct { CodeRegistrationLimit int MaxConcurrentJobs int LoraWAN client.LoraWAN - DeviceCh chan device.Device - DoneCh chan struct{} } -// Worker attempts to register a valid DevEUI via external LoRaWAN API. -// If successful, a RegisterDevice struct with its Identifier and Code will be sent to the work channel. -// -// # Example -// -// Identifier: 1CEB0080F074F750, Code: 4F750 -// -// When an unexpected error occurs, return ctx.Err instead. -func (cp *CodeProcessor) Worker(ctx context.Context, workCh chan device.Device) error { +func (cp *CodeProcessor) Start(ctx context.Context, cancel context.CancelFunc) { + workCh := make(chan struct{}) + count := 0 + go func(ctx context.Context) { + for { + cp.doWork(ctx, workCh) + } + }(ctx) + for { select { case <-ctx.Done(): - return ctx.Err() + return case <-workCh: - registeredDevice, err := cp.LoraWAN.Send(ctx) - if err == nil { - cp.DeviceCh <- *registeredDevice - - } else { - return err + count++ + if count == cp.CodeRegistrationLimit { + cancel() + fmt.Printf("work complete \n") } } } } + +func (cp *CodeProcessor) doWork(ctx context.Context, workCh chan<- struct{}) { + device, err := cp.LoraWAN.RegisterDevice(ctx) + if err != nil { + return + } else { + device.Print() + workCh <- struct{}{} + } +} From 8da5472588e2fa4e3254a521113d18b166d054b9 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sat, 13 May 2023 13:45:29 +0100 Subject: [PATCH 82/83] rename --- main.go | 6 ++---- processor/code_processor.go | 10 +++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index 619f3c5..c9bccb2 100644 --- a/main.go +++ b/main.go @@ -52,17 +52,15 @@ func main() { httpClient := &http.Client{ Timeout: time.Second * time.Duration(timeout), } - loraWAN := client.NewLoraWAN(baseurl, httpClient) // setup processor to do work - codeProcessor := &processor.CodeProcessor{ + processor := &processor.Processor{ CodeRegistrationLimit: codeRegistrationLimit, MaxConcurrentJobs: maxConcurrentJobs, LoraWAN: *loraWAN, } ctx, cancel := context.WithCancel(context.Background()) - - codeProcessor.Start(ctx, cancel) + processor.Start(ctx, cancel) } diff --git a/processor/code_processor.go b/processor/code_processor.go index 5812f6e..5665b35 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -7,18 +7,18 @@ import ( "github.com/NickGowdy/deveui-cli/client" ) -type CodeProcessor struct { +type Processor struct { CodeRegistrationLimit int MaxConcurrentJobs int LoraWAN client.LoraWAN } -func (cp *CodeProcessor) Start(ctx context.Context, cancel context.CancelFunc) { +func (p *Processor) Start(ctx context.Context, cancel context.CancelFunc) { workCh := make(chan struct{}) count := 0 go func(ctx context.Context) { for { - cp.doWork(ctx, workCh) + p.doWork(ctx, workCh) } }(ctx) @@ -28,7 +28,7 @@ func (cp *CodeProcessor) Start(ctx context.Context, cancel context.CancelFunc) { return case <-workCh: count++ - if count == cp.CodeRegistrationLimit { + if count == p.CodeRegistrationLimit { cancel() fmt.Printf("work complete \n") } @@ -36,7 +36,7 @@ func (cp *CodeProcessor) Start(ctx context.Context, cancel context.CancelFunc) { } } -func (cp *CodeProcessor) doWork(ctx context.Context, workCh chan<- struct{}) { +func (cp *Processor) doWork(ctx context.Context, workCh chan<- struct{}) { device, err := cp.LoraWAN.RegisterDevice(ctx) if err != nil { return From dad1f98224e209ff24f1352ee009ebfd0935ebe9 Mon Sep 17 00:00:00 2001 From: Nick Gowdy Date: Sat, 13 May 2023 15:48:24 +0100 Subject: [PATCH 83/83] get it work sequentially --- processor/code_processor.go | 61 ++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/processor/code_processor.go b/processor/code_processor.go index 5665b35..143c881 100644 --- a/processor/code_processor.go +++ b/processor/code_processor.go @@ -2,7 +2,7 @@ package processor import ( "context" - "fmt" + "log" "github.com/NickGowdy/deveui-cli/client" ) @@ -14,34 +14,45 @@ type Processor struct { } func (p *Processor) Start(ctx context.Context, cancel context.CancelFunc) { - workCh := make(chan struct{}) + // workCh := make(chan struct{}) count := 0 - go func(ctx context.Context) { - for { - p.doWork(ctx, workCh) - } - }(ctx) - for { - select { - case <-ctx.Done(): - return - case <-workCh: + for count < p.CodeRegistrationLimit { + device, err := p.LoraWAN.RegisterDevice(ctx) + if err != nil { + log.Print(err) + } else { + device.Print() count++ - if count == p.CodeRegistrationLimit { - cancel() - fmt.Printf("work complete \n") - } } } -} -func (cp *Processor) doWork(ctx context.Context, workCh chan<- struct{}) { - device, err := cp.LoraWAN.RegisterDevice(ctx) - if err != nil { - return - } else { - device.Print() - workCh <- struct{}{} - } + // go func(ctx context.Context) { + // for { + // p.doWork(ctx, workCh) + // } + // }(ctx) + + // for { + // select { + // case <-ctx.Done(): + // return + // case <-workCh: + // count++ + // if count == p.CodeRegistrationLimit { + // cancel() + // fmt.Printf("work complete \n") + // } + // } + // } } + +// func (cp *Processor) doWork(ctx context.Context, workCh chan<- struct{}) { +// device, err := cp.LoraWAN.RegisterDevice(ctx) +// if err != nil { +// return +// } else { +// device.Print() +// workCh <- struct{}{} +// } +// }