Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement parser #14

Merged
merged 18 commits into from
Dec 16, 2017
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ language: go
go:
- 1.7
- 1.8
- 1.9
after_success:
- test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash
106 changes: 106 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package tmsh

import (
"fmt"
"reflect"
"strconv"
"strings"
)

func Unmarshal(data string, out interface{}) error {
data = strings.Trim(data, "\n")

l := Lexer{s: NewScanner(data)}
if yyParse(&l) != 0 {
return fmt.Errorf("Parse error")
}

v := reflect.ValueOf(out)
if v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}

unmarshal(l.result, v)

return nil
}

func unmarshal(n *node, out reflect.Value) {
switch n.kind {
case ltmNodeNode, ltmPoolNode, ltmVirtualNode:
unmarshal(n.children[0], out)
case structNode:
decodeStructNode(n, out)
case keyNode:
decodeKeyNode(n, out)
case scalarNode:
decodeScalarNode(n, out)
default:
panic("Unknown node kind")
}
}

func decodeStructNode(n *node, out reflect.Value) {
l := len(n.children)

switch out.Kind() {
case reflect.Struct:
for _, c := range n.children {
unmarshal(c, out)
}
case reflect.Slice:
out.Set(reflect.MakeSlice(out.Type(), l, l))
et := out.Type().Elem()
for i := 0; i < l; i++ {
e := reflect.New(et).Elem()
unmarshal(n.children[i], e)
out.Index(i).Set(e)
}
case reflect.Map:
out.Set(reflect.MakeMap(out.Type()))
et := out.Type().Elem()
for i := 0; i < l; i++ {
k := reflect.ValueOf(n.children[i].value)
v := reflect.New(et).Elem()
unmarshal(n.children[i], v)
out.SetMapIndex(k, v)
}
}
}

func decodeKeyNode(n *node, out reflect.Value) {
switch out.Kind() {
case reflect.Struct:
if f, ok := lookupField(n.value, out); ok {
unmarshal(n.children[0], f)
} else {
for _, c := range n.children {
unmarshal(c, out)
}
}
case reflect.String:
unmarshal(n.children[0], out)
}
}

func decodeScalarNode(n *node, out reflect.Value) {
switch out.Kind() {
case reflect.Int:
i, _ := strconv.ParseInt(n.value, 10, 64)
out.SetInt(i)
case reflect.String:
out.SetString(n.value)
}
}

func lookupField(t string, v reflect.Value) (reflect.Value, bool) {
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
fi := typ.Field(i)
tagv := fi.Tag.Get("ltm")
if tagv == t {
return v.Field(i), true
}
}
return reflect.Value{}, false
}
186 changes: 79 additions & 107 deletions ltm_parse_test.go → decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,33 @@ import (
"testing"
)

func TestCurrentLine(t *testing.T) {
str := `line1
line2
line3
line4`

fm := NewFieldManager(str)

actual := fm.CurrentLine()
expect := "line1"

if !reflect.DeepEqual(actual, expect) {
t.Errorf("got %v\nwant %v", actual, expect)
}

fm.currentLineNum += 2

actual = fm.CurrentLine()
expect = "line3"

if !reflect.DeepEqual(actual, expect) {
t.Errorf("got %v\nwant %v", actual, expect)
}
}

func TestAdvance(t *testing.T) {
str := `line1
line2
line3
line4`

fm := NewFieldManager(str)
fm.Advance()
fm.Advance()
fm.Advance()

actual := fm.currentLineNum
expect := 3

if !reflect.DeepEqual(actual, expect) {
t.Errorf("got %v\nwant %v", actual, expect)
}
}

func TestParseShowLtmNode(t *testing.T) {
func TestUnmarshalNode(t *testing.T) {
str := `ltm node dev-web01.example.com {
addr 192.0.2.1
cur-sessions 0
monitor-rule none
monitor-status unchecked
name dev-web01.example.com
serverside.bits-in 0
serverside.bits-out 0
serverside.cur-conns 0
serverside.max-conns 0
serverside.pkts-in 0
serverside.pkts-out 0
serverside.tot-conns 0
session-status enabled
status.availability-state unknown
status.enabled-state enabled
status.status-reason Node address does not have service checking enabled
tot-requests 0
addr 192.0.2.1
cur-sessions 0
monitor-rule none
monitor-status unchecked
name dev-web01.example.com
serverside.bits-in 0
serverside.bits-out 0
serverside.cur-conns 0
serverside.max-conns 0
serverside.pkts-in 0
serverside.pkts-out 0
serverside.tot-conns 0
session-status enabled
status.availability-state unknown
status.enabled-state enabled
status.status-reason Node address does not have service checking enabled
tot-requests 0
}`

node := ParseShowLtmNode(str)
var node Node
if err := Unmarshal(str, &node); err != nil {
t.Errorf("got %v", err)
}

expect := &Node{
expect := Node{
Addr: "192.0.2.1",
Name: "dev-web01.example.com",
MonitorRule: "none",
Expand All @@ -85,7 +44,7 @@ func TestParseShowLtmNode(t *testing.T) {
}
}

func TestParseShowLtmPool(t *testing.T) {
func TestUnmarshalPool(t *testing.T) {
//# show ltm pool api.example.com_8080 members field-fmt
str := `ltm pool api.example.com_8080 {
active-member-cnt 2
Expand Down Expand Up @@ -174,69 +133,82 @@ func TestParseShowLtmPool(t *testing.T) {
tot-requests 0
}`

pool := ParseShowLtmPool(str)

poolMembers := []PoolMember{
PoolMember{
Name: "api01.example.com",
Addr: "192.0.2.1",
Port: 8080,
MonitorRule: "/Common/tcp (pool monitor)",
MonitorStatus: "up",
EnabledState: "enabled",
AvailabilityState: "available",
StatusReason: "Pool member is available",
},
PoolMember{
Name: "api02.example.com",
Addr: "192.0.2.2",
Port: 8080,
MonitorRule: "none",
MonitorStatus: "unchecked",
EnabledState: "disabled",
AvailabilityState: "unknown",
StatusReason: "Pool member does not have service checking enabled",
},
var pool Pool
if err := Unmarshal(str, &pool); err != nil {
t.Errorf("got %v", err)
}

expect := &Pool{
expect := Pool{
ActiveMemberCount: 2,
MonitorRule: "/Common/tcp",
Name: "api.example.com_8080",
AvailabilityState: "available",
EnabledState: "enabled",
StatusReason: "The pool is available",
PoolMembers: poolMembers,
PoolMembers: []PoolMember{
PoolMember{
Name: "api01.example.com",
Addr: "192.0.2.1",
Port: 8080,
MonitorRule: "/Common/tcp (pool monitor)",
MonitorStatus: "up",
EnabledState: "enabled",
AvailabilityState: "available",
StatusReason: "Pool member is available",
},
PoolMember{
Name: "api02.example.com",
Addr: "192.0.2.2",
Port: 8080,
MonitorRule: "none",
MonitorStatus: "unchecked",
EnabledState: "disabled",
AvailabilityState: "unknown",
StatusReason: "Pool member does not have service checking enabled",
},
},
}

if !reflect.DeepEqual(pool, expect) {
t.Errorf("\ngot %v\nwant %v", pool, expect)
}
}

func TestParseListLtmVirtual(t *testing.T) {
//# list ltm virtual api.example.com_80
str := `ltm virtual api.example.com_80 {
destination 203.0.113.1:http
ip-protocol tcp
mask 255.255.255.255
partition partition1
pool api.example.com_80
profiles {
/Common/tcp { }
}
source 0.0.0.0/0
vs-index 1234
func TestUnmarshalVirtualServer(t *testing.T) {
//# list ltm virtual api.example.com_443
str := `ltm virtual api.example.com_443 {
destination 203.0.113.1:https
ip-protocol tcp
mask 255.255.255.255
partition partition1
pool api.example.com_443
profiles {
/Common/tcp {
context all
}
wildcard.example.com {
context clientside
}
}
source 0.0.0.0/0
vs-index 1234
}`

vs := ParseListLtmVirtual(str)
var vs VirtualServer
if err := Unmarshal(str, &vs); err != nil {
t.Errorf("got %v", err)
}

expect := &VirtualServer{
Destination: "203.0.113.1:http",
expect := VirtualServer{
Destination: "203.0.113.1:https",
IpProtocol: "tcp",
Mask: "255.255.255.255",
Partition: "partition1",
Pool: "api.example.com_80",
Pool: "api.example.com_443",
Profiles: map[string]Profile{
"/Common/tcp": Profile{Context: "all"},
"wildcard.example.com": Profile{Context: "clientside"},
},
}

if !reflect.DeepEqual(vs, expect) {
Expand Down
Loading