Graceful Shutdown inn Golang - amazon-web-services

I am trying to add Graceful shut down in one of my server.
I know the code is working.
I was hoping that after redeployment of the server, first the graceful shutdown work and then the server would restart .
But it is not happening.
Can you all tell me the possibile reason's.
done := make(chan bool, 1)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
signal.Notify(quit, syscall.SIGHUP, syscall.SIGQUIT)
signal.Notify(quit, syscall.SIGILL, syscall.SIGTRAP)
signal.Notify(quit, syscall.SIGABRT, syscall.SIGBUS, syscall.SIGFPE)
go func() {
BootUpLog("before <-quit")
syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
<-quit
BootUpLog("shutting down")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
BootUpLog(fmt.Sprintf("Error in graceful shutdown of the server: %v", err))
}
close(done)
}()
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
BootUpLog(fmt.Sprintf("shut down with error: %v", err))
}
<-done
BootUpLog("Shutdown gracefully.")
}
Here the log "before <-quit" is printing .
And after redeploymet it is not receiving any signal in the <-quit channel.

Could there be a problem with your BootUpLog() implementation? The following works for me (main.go):
package main
import (
"context"
"fmt"
"html"
"net/http"
"os"
"os/signal"
"syscall"
)
func main() {
fmt.Println("starting up")
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
signal.Notify(quit, syscall.SIGHUP, syscall.SIGQUIT)
signal.Notify(quit, syscall.SIGILL, syscall.SIGTRAP)
signal.Notify(quit, syscall.SIGABRT, syscall.SIGBUS, syscall.SIGFPE)
server := &http.Server{
Addr: ":8080",
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
down := make(chan struct{})
go func() {
defer close(down)
err := server.ListenAndServe()
fmt.Println("shutting down: ", err)
}()
go func() {
fmt.Println("before <- quit")
syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
fmt.Println("after signal sent")
<-quit
fmt.Println("after signal received")
server.Shutdown(context.Background())
}()
<-down
}
Running it:
$ go run main.go
starting up
before <- quit
after signal sent
after signal received
shutting down: http: Server closed

Related

Create unit test for WS in golang using Echo router

I use gorilla/websocket for ws and labstack/echo as router. I need to create unit test for the handler. I find topic with solving this problem with default go router, but i don't understand how to use it with echo.
I have this:
func TestWS(t *testing.T){
provider := handler.New(coordinateservice.New())
e := echo.New()
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/admin/orders/:id/details", nil)
c := e.NewContext(req, rec)
c.SetPath("/admin/orders/:id/details")
c.SetParamNames("id")
c.SetParamValues("9999")
if assert.NoError(t, provider.OrderHandler.OpenWs(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
}
u := url.URL{Scheme: "ws", Host: "127.0.0.1", Path: "/admin/orders/9999/details"}
fmt.Println(u.String())
// Connect to the server
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
t.Fatalf("%v", err)
}
defer ws.Close()
_, p, err := ws.ReadMessage()
if err != nil {
t.Fatalf("%v", err)
}
fmt.Println(string(p))
}
And error websocket: the client is not using the websocket protocol: 'upgrade' token not found in 'Connection' header in this line:
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
t.Fatalf("%v", err)
}
What i need to do for connecting ws to the echo handler?
The this is my golang unit test websocket example.
The stability of my httptest library is not guaranteed, but you can refer to using net.Pipe to create a connection for ws.
use echo:
func main() {
app := echo.New()
app.GET("/", hello)
client := httptest.NewClient(app)
go func() {
// use http.Handler, if not has host.
client.NewRequest("GET", "/example/wsio").WithWebsocket(handlerGobwasWebsocket).Do().Out()
// use network
client.NewRequest("GET", "http://localhost:8088/example/wsio").WithWebsocket(handlerGobwasWebsocket).Do().Out()
}()
app.Start(":8088")
}
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
func handlerGobwasWebsocket(conn net.Conn) {
go func() {
wsutil.WriteClientBinary(conn, []byte("aaaaaa"))
wsutil.WriteClientBinary(conn, []byte("bbbbbb"))
wsutil.WriteClientBinary(conn, []byte("ccccc"))
}()
defer conn.Close()
for {
b, err := wsutil.ReadServerBinary(conn)
fmt.Println("ws io client read: ", string(b), err)
if err != nil {
fmt.Println("gobwas client err:", err)
return
}
}
}

Unit testing a Router provider in Go

I have a file in my project as :
package handlers
import (
"github.com/gorilla/mux"
)
type IHandlerProvider interface {
GetRouter() *mux.Router
}
type HandlerProvider struct{}
func (h HandlerProvider) GetRouter() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/health", Health).Methods("GET")
return r
}
What is the right way to unit test this? For instance:
package handlers
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetRouterOk(t *testing.T) {
var subject IHandlerProvider = HandlerProvider{}
router := subject.GetRouter()
assert.NotNil(t, router)
}
I can assert the object not being null, but how can I test the routes are correct?
If you want to test that the router is returning expected handler (vs test behaviour), you can do something like the following:
r := mux.NewRouter()
r.HandleFunc("/a", handlerA).Methods("GET")
r.HandleFunc("/b", handlerB).Methods("GET")
req, err := httptest.NewRequest("GET", "http://example.com/a", nil)
require.NoError(err, "create request")
m := &mux.RouteMatch{}
require.True(r.Match(req, m), "no match")
v1 := reflect.ValueOf(m.Handler)
v2 := reflect.ValueOf(handlerA)
require.Equal(v1.Pointer(), v2.Pointer(), "wrong handler")
You could use httptest package.
handlers.go:
package handlers
import (
"net/http"
"github.com/gorilla/mux"
)
type IHandlerProvider interface {
GetRouter() *mux.Router
}
type HandlerProvider struct {}
func Health(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}
func (h HandlerProvider) GetRouter() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/health", Health).Methods("GET")
return r
}
handlers_test.go:
package handlers
import (
"testing"
"bytes"
"io/ioutil"
"net/http/httptest"
)
func TestGetRouterOk(t *testing.T) {
assertResponseBody := func(t *testing.T, s *httptest.Server, expectedBody string) {
resp, err := s.Client().Get(s.URL+"/health")
if err != nil {
t.Fatalf("unexpected error getting from server: %v", err)
}
if resp.StatusCode != 200 {
t.Fatalf("expected a status code of 200, got %v", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("unexpected error reading body: %v", err)
}
if !bytes.Equal(body, []byte(expectedBody)) {
t.Fatalf("response should be ok, was: %q", string(body))
}
}
var subject IHandlerProvider = HandlerProvider{}
router := subject.GetRouter()
s := httptest.NewServer(router)
defer s.Close()
assertResponseBody(t, s, "ok")
}
unit test result:
=== RUN TestGetRouterOk
--- PASS: TestGetRouterOk (0.00s)
PASS
ok github.com/mrdulin/golang/src/stackoverflow/64584472 0.097s
coverage:

Testing with Gomock returns error: Expected call has already been called the max number of times

I am using Gomock https://godoc.org/github.com/golang/mock and mockgen
The Source code for this test is:
package sqs
import (
"fmt"
"log"
"os"
"runtime"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/sqs"
"github.com/aws/aws-sdk-go/service/sqs/sqsiface"
)
var sess *session.Session
var svc *sqs.SQS
var queueURL string
func init() {
// Setting the runtime to run with max CPUs available
runtime.GOMAXPROCS(runtime.NumCPU())
sess = session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
}))
svc = sqs.New(sess)
queueURL = os.Getenv("QUEUE_URL")
}
type Poller interface {
Poll(chan bool)
}
// NewPoller is a factory to create a Poller object
func NewPoller(msgr Messenger) Poller {
p := &poller{
m: msgr,
}
return p
}
type poller struct {
m Messenger
}
func (p *poller) Poll(done chan bool) {
sqsMsgCh := make(chan *sqs.Message, 100)
for {
messages, err := p.m.GetMessage()
if err != nil {
log.Printf("error when getting message")
if len(messages) == 0 {
// Stop the system
log.Printf("I am here")
done <- true
}
}
for _, msg := range messages {
sqsMsgCh <- msg
}
}
}
type Messenger interface {
GetMessage() ([]*sqs.Message, error)
}
func NewMessenger() Messenger {
return &messenger{
s: svc,
}
}
type messenger struct {
s sqsiface.SQSAPI
}
func (m *messenger) GetMessage() ([]*sqs.Message, error) {
result, err := m.s.ReceiveMessage(&sqs.ReceiveMessageInput{
AttributeNames: []*string{
aws.String(sqs.MessageSystemAttributeNameSentTimestamp),
},
MessageAttributeNames: []*string{
aws.String(sqs.QueueAttributeNameAll),
},
QueueUrl: aws.String(queueURL),
MaxNumberOfMessages: aws.Int64(10),
VisibilityTimeout: aws.Int64(36000), // 10 hours
WaitTimeSeconds: aws.Int64(0),
})
if err != nil {
fmt.Println("Error", err)
return nil, err
}
msgs := result.Messages
if len(msgs) == 0 {
fmt.Println("Received no messages")
return msgs, err
}
return msgs, nil
}
The test case for this Source file is here:
package sqs
import (
"errors"
"testing"
"path_to_the_mocks_package/mocks"
"github.com/golang/mock/gomock"
"github.com/aws/aws-sdk-go/service/sqs"
)
func TestPollWhenNoMessageOnQueue(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
msgr := mocks.NewMockMessenger(mockCtrl)
mq := make([]*sqs.Message, 1)
err := errors.New("Mock Error")
// msgr.EXPECT().GetMessage().Return(mq, err) //.Times(1)
// msgr.GetMessage().Return(mq, err) //.Times(1)
msgr.EXPECT().GetMessage().Return(mq, err)
p := NewPoller(msgr)
done := make(chan bool)
go p.Poll(done)
<-done
t.Logf("Successfully done: %v", done)
}
When I run the tests I am getting the following error:
sqs\controller.go:150: Unexpected call to
*mocks.MockMessenger.GetMessage([]) at path_to_mocks_package/mocks/mock_messenger.go:38 because: Expected
call at path_to_sqs_package/sqs/sqs_test.go:35 has already been called
the max number of times. FAIL
If I write my own mock as follows the test case executes successfully:
type mockMessenger struct {
mock.Mock
}
func (m *mockMessenger) GetMessage() ([]*sqs.Message, error) {
msgs := make([]*sqs.Message, 0)
err := errors.New("Error")
return msgs, err
}
You are implicitly telling gomock that you only expect a single call.
msgr.EXPECT().GetMessage().Return(mq, err)
Adding a number of Times to the mock, allows you to return those values more than once.
msgr.EXPECT().GetMessage().Return(mq, err).AnyTimes()
For more details please read the gomock's AnyTimes documentation.

Golang Escape Question Mark Character for Cisco Regex

So, Cisco's regex allows the question mark character. But the catch is that you have to precede typing a question mark with Ctrl-Shift-v in order for it to be interpreted as a question mark and not a help command... Link to Cisco regex guidelines
I have a Go program that logs into a set of devices and runs a set of commands on each device. When trying to use a regex containing a question mark, though, the Cisco device always interprets the question mark as a help command. Using string literals in Go does not fix the problem nor does sending the command as a slice of bytes.
For example, if I try to send the command show boot | include (c|cat)[0-9]+[a-zA-Z]? the Cisco CLI returns
switch-1#show boot | include (c|cat)[0-9]+[a-zA-Z]?
LINE <cr>
switch-1#
instead of interpreting the question mark as a regex match of 0 or 1 for the [a-zA-Z] group.
However, using the command ssh user#switch-1 'show boot | include (c|cat)[0-9]+[a-zA-Z]?' works as expected and interprets the regex pattern correctly.
How can I replicate the behaviour of the ssh command? Is there a way to send Ctrl-Shift-v before each question mark or escape each question mark character?
My code as requested:
package main
import (
"golang.org/x/crypto/ssh"
"net"
"fmt"
"os"
"bufio"
"time"
"golang.org/x/crypto/ssh/terminal"
"io"
"io/ioutil"
"sync"
"strings"
)
// ReadLines reads a file line-by-line and returns a slice of the lines.
func ReadLines(filename string) ([]string, error) {
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %v", err)
}
defer f.Close()
var lines []string
s := bufio.NewScanner(f)
for s.Scan() {
lines = append(lines, s.Text())
}
if err := s.Err(); err != nil {
return nil, err
}
return lines, nil
}
// Type Result represents the result of running the Configure method.
type Result struct {
Host string // Hostname of device
Output []byte // Remote shell's stdin and stderr output
Err error // Remote shell errors
}
// Configure logs into a device, starts a remote shell, runs the set of
// commands, and waits for the remote shell to return or timeout.
func Configure(host string, config *ssh.ClientConfig, cmds []string, results chan<- *Result, wg *sync.WaitGroup) {
defer wg.Done()
res := &Result{
Host: host,
Output: nil,
Err: nil,
}
// Create client connection
client, err := ssh.Dial("tcp", net.JoinHostPort(host, "22"), config)
if err != nil {
res.Err = fmt.Errorf("failed to dial: %v", err)
results <- res
return
}
defer client.Close()
// Create new session
session, err := client.NewSession()
if err != nil {
res.Err = fmt.Errorf("failed to create session: %v", err)
results <- res
return
}
defer session.Close()
// Set session IO
stdin, err := session.StdinPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stdin: %v", err)
results <- res
return
}
defer stdin.Close()
stdout, err := session.StdoutPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stdout: %v", err)
results <- res
return
}
stderr, err := session.StderrPipe()
if err != nil {
res.Err = fmt.Errorf("failed to create pipe to stderr: %v", err)
results <- res
return
}
// Start remote shell
if err := session.RequestPty("vt100", 0, 0, ssh.TerminalModes{
ssh.ECHO: 0,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}); err != nil {
res.Err = fmt.Errorf("failed to request pseudoterminal: %v", err)
results <- res
return
}
if err := session.Shell(); err != nil {
res.Err = fmt.Errorf("failed to start remote shell: %v", err)
results <- res
return
}
// Run commands
for _, cmd := range cmds {
if _, err := io.WriteString(stdin, cmd+"\n"); err != nil {
res.Err = fmt.Errorf("failed to run: %v", err)
results <- res
return
}
}
// Wait for remote commands to return or timeout
exit := make(chan error, 1)
go func(exit chan<- error) {
exit <- session.Wait()
}(exit)
timeout := time.After(1 * time.Minute)
select {
case <-exit:
output, err := ioutil.ReadAll(io.MultiReader(stdout, stderr))
if err != nil {
res.Err = fmt.Errorf("failed to read output: %v", err)
results <- res
return
}
res.Output = output
results <- res
return
case <-timeout:
res.Err = fmt.Errorf("session timed out")
results <- res
return
}
}
func main() {
hosts, err := ReadLines(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
cmds, err := ReadLines(os.Args[2])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Fprint(os.Stderr, "Password: ")
secret, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read password: %v\n", err)
os.Exit(1)
}
fmt.Fprintln(os.Stderr)
config := &ssh.ClientConfig{
User: "user",
Auth: []ssh.AuthMethod{ssh.Password(string(secret))},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 30 * time.Second,
}
config.SetDefaults()
config.Ciphers = append(config.Ciphers, "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc")
results := make(chan *Result, len(hosts))
var wg sync.WaitGroup
wg.Add(len(hosts))
for _, host := range hosts {
go Configure(host, config, cmds, results, &wg)
}
wg.Wait()
close(results)
for res := range results {
if res.Err != nil {
fmt.Fprintf(os.Stderr, "Error %s: %v\n", res.Host, res.Err)
continue
}
fmt.Printf("Host %s\n%s\n%s\n", res.Host, res.Output, strings.Repeat("-", 50))
}
}
Try forcing the IOS terminal server into line mode (as opposed to character mode). Send these telnet negotiation sequences:
IAC DONT ECHO
IAC WONT ECHO
IAC DONT SUPPRESS-GO-AHEAD
IAC WONT SUPPRESS-GO-AHEAD
See: https://www.rfc-editor.org/rfc/rfc858

Channel race condition

I'm trying to write tests for a simple program that reads events from the inotify bus, filters them, then puts them on a channel. I have seen no issues when actually running the code, but in the last line of the following test, it will occasionally deadlock (about 30% of the time) on reading from that channel, even though as best I can tell, the event is always being put on the channel.
Here is the function:
func eventFilter(watcher *rfsnotify.RWatcher, excludes []string, out chan<- fsnotify.Event) {
for {
select {
case event := <-watcher.Events:
log.Debug(fmt.Sprintf("Got event %v", event))
if isExcluded(event.Name, excludes) {
log.Info("Ignoring excluded file: %v", event)
} else if isRelevantOp(event) {
log.Info(fmt.Sprintf("Handling event %v", event))
out <- event
} else {
log.Info(fmt.Sprintf("Ignoring event %v", event))
}
case err := <-watcher.Errors:
log.Error(fmt.Sprintf("Error: %v", err))
}
}
}
Here is the testing code:
var hook *logrusTest.Hook
var testDir string
var rWatcher *rfsnotify.RWatcher
func TestMain(m *testing.M) {
// initialize bad globals, amongst other things
log.SetLevel(log.DebugLevel)
hook = logrusTest.NewGlobal()
// Create directory to watch
testDir = tempMkdir()
defer os.RemoveAll(testDir)
rWatcher, _ = rfsnotify.NewWatcher()
defer rWatcher.Close()
err := rWatcher.AddRecursive(testDir)
if err != nil {
log.Fatal("Failed to add watcher: ", err)
}
os.Exit(m.Run())
}
func TestEventFilterMove(t *testing.T) {
ch := make(chan fsnotify.Event, 10)
go eventFilter(rWatcher, []string{"test"}, ch)
testFileSrc := filepath.Join(testDir, "TestFsnotifyEvents.testfileSrc")
testFileDest := filepath.Join(testDir, "TestFsnotifyEvents.testfileDest")
f, err := os.OpenFile(testFileSrc, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatalf("creating test file failed: %s", err)
}
f.WriteString("data")
f.Sync()
hook.Reset()
err = os.Rename(testFileSrc, testFileDest)
if err != nil {
t.Fatalf("renaming test file failed: %s", err)
}
time.Sleep(100 * time.Millisecond)
expectedLogs := []string{
fmt.Sprintf("Got event \"%v\": CREATE|UPDATE", testFileDest),
fmt.Sprintf("Handling event \"%v\": CREATE|UPDATE", testFileDest),
fmt.Sprintf("Got event \"%v\": RENAME", testFileSrc),
fmt.Sprintf("Ignoring event \"%v\": RENAME", testFileSrc),
}
var actualLogs []string
for _, logEntry := range hook.AllEntries() {
actualLogs = append(actualLogs, logEntry.Message)
}
assert.ElementsMatch(t, actualLogs, expectedLogs)
hook.Reset()
_ = <-ch
assert.Equal(t, 0, len(ch))
}