Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ type HAProxyClient struct {
conn net.Conn
}

type HAProxy interface {
RunCommand(cmd string) (*bytes.Buffer, error)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be easier to pass the net.Conn reference to the HAProxy struct and move the dial and schema functions into helper functions instead?

This would decouple the implementation and external dependencies at the API boundary and could be done externally without having to export extra types.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it six of one and half dozen another. The net.Conn interface is 8 functions which is considerable to implement for just a stub. The benefit though is that if the proxy expands on any of the functions that net.Conn supplies the previously implemented stub would be easy to extend.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benjic while it would be possible to pass the net.Conn reference in, the dial function(and possibly schema) would have to be called on every RunCommand, as the connection is immediately closed following response.

}

// RunCommand is the entrypoint to the client. Sends an arbitray command string to HAProxy.
func (h *HAProxyClient) RunCommand(cmd string) (*bytes.Buffer, error) {
err := h.dial()
Expand Down
57 changes: 15 additions & 42 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,25 @@
package haproxy_test
package haproxy

import (
"fmt"

"github.com/bcicen/go-haproxy"
"testing"
)

func ExampleHAProxyClient_Stats() {
client := &haproxy.HAProxyClient{
Addr: "unix:///var/run/haproxy.sock",
}
stats, err := client.Stats()
if err != nil {
fmt.Println(err)
return
}
for _, s := range stats {
fmt.Printf("%s: %s\n", s.SvName, s.Status)
}
// Output:
//static: DOWN
//app1: UP
//app2: UP
//...
}
// TestSchemaValidation ensures that the schema() method correctly parses Addr strings.
func TestSchemaValidation(t *testing.T) {
ha := &HAProxyClient{Addr: "tcp://sys49152/"}

func ExampleHAProxyClient_Info() {
client := &haproxy.HAProxyClient{
Addr: "unix:///var/run/haproxy.sock",
if ha.schema() != "tcp" {
t.Errorf("Expected 'tcp', received '%s'", ha)
}
info, err := client.Info()
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s version %s\n", info.Name, info.Version)
// Output:
//HAProxy version 1.6.3
}

func ExampleHAProxyClient_RunCommand() {
client := &haproxy.HAProxyClient{
Addr: "unix:///var/run/haproxy.sock",
ha = &HAProxyClient{Addr: "unix://sys2064/"}
if ha.schema() != "socket" {
t.Errorf("Expected 'socket', received '%s'", ha)
}
result, err := client.RunCommand("show info")
if err != nil {
fmt.Println(err)
return

ha = &HAProxyClient{Addr: "unknown://RUN/"}
if ha.schema() != "" {
t.Errorf("Expected '', received '%s'", ha)
}
fmt.Println(result.String())

}
6 changes: 3 additions & 3 deletions info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

// Response from HAProxy "show info" command.
type Info struct {
type InfoResponse struct {
Name string `kv:"Name"`
Version string `kv:"Version"`
ReleaseDate string `kv:"Release_date"`
Expand Down Expand Up @@ -59,12 +59,12 @@ type Info struct {
}

// Equivalent to HAProxy "show info" command.
func (h *HAProxyClient) Info() (*Info, error) {
func Info(h HAProxy) (*InfoResponse, error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change to the existing API. It may be trivial but is worth noting.

res, err := h.RunCommand("show info")
if err != nil {
return nil, err
}
info := &Info{}
info := &InfoResponse{}
err = kvcodec.Unmarshal(res, info)
if err != nil {
return nil, fmt.Errorf("error decoding response: %s", err)
Expand Down
48 changes: 48 additions & 0 deletions info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package haproxy

import (
"bytes"
"testing"
)

type InfoTestHAProxyClient struct{}

// RunCommand stubs the HAProxyClient returning our expected bytes.Buffer containing the response from a 'show info' command.
func (ha *InfoTestHAProxyClient) RunCommand(cmd string) (*bytes.Buffer, error) {
var buf bytes.Buffer
buf.WriteString("Name: HAProxy\n")
buf.WriteString("Version: 1.5.4\n")
buf.WriteString("node: SYS64738\n")
buf.WriteString("description: go-haproxy stub tests.\n")
return &buf, nil
}

// TestCommandInfo validates the structure of the "show info" command is handled appropriately.
func TestCommandInfo(t *testing.T) {
ha := new(InfoTestHAProxyClient)
info, err := Info(ha)

if err != nil {
t.Fatalf("Unable to execute 'show info' Info()")
}

expect := "HAProxy"
if info.Name != expect {
t.Errorf("Expected Name of '%s', but received '%s' instead", expect, info.Name)
}

expect = "1.5.4"
if info.Version != expect {
t.Errorf("Expected Version of '%s', but received '%s' instead", expect, info.Version)
}

expect = "SYS64738"
if info.Node != expect {
t.Errorf("Expected Node of '%s', but received '%s' instead", expect, info.Node)
}

expect = "go-haproxy stub tests."
if info.Description != expect {
t.Errorf("Expected Description of '%s', but received '%s' instead", expect, info.Description)
}
}
4 changes: 2 additions & 2 deletions stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

// Response from HAProxy "show stat" command.
type Stat struct {
type StatResponse struct {
PxName string `csv:"# pxname"`
SvName string `csv:"svname"`
Qcur uint64 `csv:"qcur"`
Expand Down Expand Up @@ -74,7 +74,7 @@ type Stat struct {
}

// Equivalent to HAProxy "show stat" command.
func (h *HAProxyClient) Stats() (stats []*Stat, err error) {
func Stats(h HAProxy) (stats []*StatResponse, err error) {
res, err := h.RunCommand("show stat")
if err != nil {
return nil, err
Expand Down
40 changes: 40 additions & 0 deletions stat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package haproxy

import (
"bytes"
"testing"
)

type StatsTestHAProxyClient struct{}

// RunCommand stubs the HAProxyClient returning our expected bytes.Buffer containing the response from a 'show stats' command.
func (ha *StatsTestHAProxyClient) RunCommand(cmd string) (*bytes.Buffer, error) {
var buf bytes.Buffer
buf.WriteString("# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,\n")
buf.WriteString("main,FRONTEND,,,0,0,3000,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,0,,,,0,0,0,0,0,0,,0,0,0,,,0,0,0,0,,,,,,,,")
return &buf, nil
}

// TestCommandStats validates the structure of the "show stats" command is handled appropriately.
func TestCommandStats(t *testing.T) {
ha := new(StatsTestHAProxyClient)
stats, err := Stats(ha)

if err != nil {
t.Fatalf("Unable to execute 'show stats' Stats()")
}

if len(stats) != 1 {
t.Errorf("Expected 1 'show stats' record, found %d", len(stats))
}

expect := "main"
if stats[0].PxName != expect {
t.Errorf("Expected PxName of '%s', but received '%s' instead", expect, stats[0].PxName)
}

expect = "FRONTEND"
if stats[0].SvName != expect {
t.Errorf("Expected SvName of '%s', but received '%s' instead", expect, stats[0].SvName)
}
}