Skip to content

Commit

Permalink
Merge pull request #150 from jainvipin/lldp
Browse files Browse the repository at this point in the history
add lldp utils
  • Loading branch information
Vipin Jain committed Sep 23, 2015
2 parents ccab55b + 25b6687 commit 6c2afe4
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 0 deletions.
5 changes: 5 additions & 0 deletions crt/crt.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func (c *CRT) GetContainerName(contID string) (string, error) {
return c.ContainerIf.GetContainerName(contID)
}

// ExecContainer executes a specified in the container's namespace
func (c *CRT) ExecContainer(contName string, cmdParams ...string) ([]byte, error) {
return c.ContainerIf.ExecContainer(contName, cmdParams...)
}

// Deinit deinitializes the container interface.
func (c *CRT) Deinit() {
c.ContainerIf.Deinit()
Expand Down
1 change: 1 addition & 0 deletions crtclient/crtclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ type ContainerIf interface {
DetachEndpoint(ctx *ContainerEPContext) error
GetContainerID(contName string) string
GetContainerName(contName string) (string, error)
ExecContainer(contName string, cmdString ...string) ([]byte, error)
}
12 changes: 12 additions & 0 deletions crtclient/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,15 @@ func (d *Docker) GetContainerName(contID string) (string, error) {

return contInfo.Name, nil
}

// ExecContainer executes a specified in the container's namespace
func (d *Docker) ExecContainer(contName string, cmdParams ...string) ([]byte, error) {
newCmdParams := append([]string{"exec", contName}, cmdParams...)
output, err := exec.Command("docker", newCmdParams...).CombinedOutput()
if err != nil {
log.Errorf("Unable to execute in container namespace. Cmd: %v Error: %s Output: \n%s\n", newCmdParams, err, output)
return nil, err
}

return output, err
}
186 changes: 186 additions & 0 deletions utils/lldp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/***
Copyright 2015 Cisco Systems Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package utils

import (
"strings"

log "github.com/Sirupsen/logrus"
"github.com/contiv/netplugin/core"
"github.com/contiv/netplugin/crt"
)

const (
invalidAttr = ""
lldpadContainerName = "lldpad"
)

// Implement utilities to fetch lldp information aobut connecting neighbors.
// The lldp information can be used in non cloud environment to automate
// networking configuration on the physical devices

// LLDPNeighbor lists the neighbor attributes as learned by the host
type LLDPNeighbor struct {
SystemName string
SystemDescription string
PortID string
PortDesc string
CiscoACIPodID string
CiscoACINodeID string
}

// FetchLLDPInfo reads the lldp information using lldptool
// A native go lib could be done but it requires that lldpad daemon is
// accessible natively on the lldp ipc path
func FetchLLDPInfo(crt *crt.CRT, ifname string) (*LLDPNeighbor, error) {
lldp := &LLDPNeighbor{}

if crt.GetContainerID(lldpadContainerName) == "" {
log.Errorf("Unable to fetch %q container information - please check if it is running", lldpadContainerName)
return nil, core.Errorf("unable to fetch container %s info", lldpadContainerName)
}

cmdWithArgs := []string{"lldptool", "-n", "-t", "-i", ifname}
output, err := crt.ExecContainer(lldpadContainerName, cmdWithArgs...)
if err != nil {
log.Errorf("Error reading lldp information. Error: %s Output: \n%s", err, output)
return nil, err
}

err = parseLLDPArgs(string(output), lldp)

return lldp, err
}

func parseLLDPArgs(lldpToolOutput string, lldp *LLDPNeighbor) error {
var err error
lines := strings.Split(lldpToolOutput, "\n")
maxIdx := len(lines) - 1
for idx := 0; idx < maxIdx; {
line := strings.Trim(lines[idx], "\n")
idx++

if !strings.Contains(line, "TLV") ||
strings.Contains(line, "End of LLDPDU TLV") {
continue
}

attrIdx := idx
for !strings.Contains(lines[attrIdx], "TLV") && attrIdx <= maxIdx {
lines[attrIdx] = strings.Trim(lines[attrIdx], "\n")
attrIdx++
}
tlvLines := lines[idx:attrIdx]
idx = attrIdx
err = nil
switch {
case strings.Contains(line, "System Name TLV"):
lldp.SystemName, err = lldpReadSystemName(tlvLines)
case strings.Contains(line, "System Description TLV"):
lldp.SystemDescription, err = lldpReadSystemDescription(tlvLines)
case strings.Contains(line, "Port ID TLV"):
lldp.PortID, err = lldpReadPortID(tlvLines)
case strings.Contains(line, "Port Description TLV"):
lldp.PortDesc, err = lldpReadPortDescription(tlvLines)
}
if err != nil {
log.Errorf("Error parsing lldp information. Error '%v'", err)
}
}
err = deriveLLDPArgs(lldp)
if err != nil {
log.Errorf("Error parsing lldp information. Error '%v'", err)
}

return err
}

func deriveLLDPArgs(lldp *LLDPNeighbor) error {
var err error

lldp.CiscoACIPodID, err = lldpReadACIPodID(lldp.SystemDescription)
if err != nil {
log.Errorf("Error parsing lldp information. Error '%v'", err)
}

lldp.CiscoACINodeID, err = lldpReadACINodeID(lldp.SystemDescription)
if err != nil {
log.Errorf("Error parsing lldp information. Error '%v'", err)
}

return err
}

func lldpReadSystemName(attrLines []string) (string, error) {
if len(attrLines) < 1 {
return invalidAttr, core.Errorf("empty attributes")
}

return strings.Trim(attrLines[0], " "), nil
}

func lldpReadSystemDescription(attrLines []string) (string, error) {
if len(attrLines) < 1 {
return invalidAttr, core.Errorf("empty attributes")
}

return strings.Trim(attrLines[0], " "), nil
}

func lldpReadPortID(attrLines []string) (string, error) {
if len(attrLines) < 1 {
return invalidAttr, core.Errorf("empty attributes")
}

words := strings.Split(attrLines[0], ":")
portID := ""
if len(words) >= 2 {
portID = strings.Trim(words[1], " ")
}
return portID, nil
}

func lldpReadPortDescription(attrLines []string) (string, error) {
if len(attrLines) < 1 {
return invalidAttr, core.Errorf("empty attributes")
}

return strings.Trim(attrLines[0], " "), nil
}

func lldpReadACIPodID(systemDesc string) (string, error) {
if !strings.Contains(systemDesc, "pod-") {
return invalidAttr, core.Errorf("unable to parse pod id from sysname")
}
words := strings.Split(systemDesc, "/")
podID := ""
if len(words) >= 2 {
podID = strings.Split(words[1], "-")[1]
}
return podID, nil
}

func lldpReadACINodeID(systemDesc string) (string, error) {
if !strings.Contains(systemDesc, "node-") {
return invalidAttr, core.Errorf("unable to parse node id from sysname")
}
words := strings.Split(systemDesc, "/")
nodeID := ""
if len(words) >= 2 {
nodeID = strings.Split(words[2], "-")[1]
}
return nodeID, nil
}
124 changes: 124 additions & 0 deletions utils/lldp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/***
Copyright 2014 Cisco Systems Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package utils

import (
"testing"
)

func TestSystemName(t *testing.T) {
attrLines := []string{" contiv-aci-leaf2"}
sysName, err := lldpReadSystemName(attrLines)

if err != nil || sysName != "contiv-aci-leaf2" {
t.Fatalf("failed to get parse lldp system name\n")
}
}

func TestSystemDescription(t *testing.T) {
attrLines := []string{" topology/pod-1/node-102"}
sysDesc, err := lldpReadSystemDescription(attrLines)

if err != nil || sysDesc != "topology/pod-1/node-102" {
t.Fatalf("failed to get parse lldp system description\n")
}
}

func TestPortDesc(t *testing.T) {
attrLines := []string{" topology/pod-1/paths-102/pathep-[eth1/11]"}
portDesc, err := lldpReadPortDescription(attrLines)

if err != nil || portDesc != "topology/pod-1/paths-102/pathep-[eth1/11]" {
t.Fatalf("failed to get parse lldp port description\n")
}
}

func TestPortID(t *testing.T) {
attrLines := []string{" Local: Eth1/11"}
portID, err := lldpReadPortID(attrLines)

if err != nil || portID != "Eth1/11" {
t.Fatalf("failed to get parse lldp port id\n")
}
}

func TestACIInfo(t *testing.T) {
podID, err := lldpReadACIPodID("topology/pod-2/node-103")
if err != nil || podID != "2" {
t.Fatalf("failed to parse lldp system description for pod id\n")
}

nodeID, err := lldpReadACINodeID("topology/pod-2/node-103")
if err != nil || nodeID != "103" {
t.Fatalf("failed to parse system info for node id\n")
}
}

func TestLLDPAttrs(t *testing.T) {
lldpToolOutput := `
Chassis ID TLV
MAC: a4:6c:2a:1e:0c:b9
Port ID TLV
Local: Eth1/11
Time to Live TLV
120
Port Description TLV
topology/pod-1/paths-102/pathep-[eth1/11]
System Name TLV
contiv-aci-leaf2
System Description TLV
topology/pod-1/node-102
System Capabilities TLV
System capabilities: Bridge, Router
Enabled capabilities: Bridge, Router
Management Address TLV
MAC: a4:6c:2a:1e:0c:b9
Ifindex: 83886080
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 201, Info: 02
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 212, Info: 53414c3139313845394e46
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 210, Info: 6e393030302d31312e3128312e32353229
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 202, Info: 01
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 205, Info: 0001
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 208, Info: 0a00505d
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 203, Info: 00000066
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 206, Info: 636f6e7469762d616369
Unidentified Org Specific TLV
OUI: 0x000142, Subtype: 207, Info: 010a00000139653462313438362d313939392
End of LLDPDU TLV`

var parsedInfo LLDPNeighbor

err := parseLLDPArgs(lldpToolOutput, &parsedInfo)
if err != nil {
t.Fatalf("failed to parse lldp attributes\n")
}
if parsedInfo.SystemDescription != "topology/pod-1/node-102" ||
parsedInfo.SystemName != "contiv-aci-leaf2" ||
parsedInfo.CiscoACIPodID != "1" ||
parsedInfo.CiscoACINodeID != "102" ||
parsedInfo.PortDesc != "topology/pod-1/paths-102/pathep-[eth1/11]" ||
parsedInfo.PortID != "Eth1/11" {
t.Fatalf("parsed params invalid %v \n", parsedInfo)
}
}

0 comments on commit 6c2afe4

Please sign in to comment.