Skip to content

Commit

Permalink
vm connections and topology (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
haim-kermany authored Oct 1, 2024
1 parent 1f032d9 commit 3359094
Show file tree
Hide file tree
Showing 8 changed files with 2,149 additions and 83 deletions.
27 changes: 26 additions & 1 deletion pkg/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ package collector

import (
"fmt"

resources "github.com/np-guard/vmware-analyzer/pkg/model/generated"
)

const (
domainsQuery = "policy/api/v1/infra/domains"
servicesQuery = "policy/api/v1/infra/services"
segmentsQuery = "policy/api/v1/infra/segments"
segmentPortsQuery = "policy/api/v1/infra/segments/%s/ports"
tier0Query = "policy/api/v1/infra/tier-0s"
tier1Query = "policy/api/v1/infra/tier-1s"
virtualMachineQuery = "api/v1/fabric/virtual-machines"
virtualInterfaceQuery = "api/v1/fabric/vifs"
groupsQuery = "policy/api/v1/infra/domains/%s/groups"
groupQuery = "policy/api/v1/infra/domains/%s/groups/%s"
groupMembersQuery = "policy/api/v1/infra/domains/%s/groups/%s/members/virtual-machines"
Expand All @@ -30,10 +36,15 @@ type serverData struct {
func CollectResources(nsxServer, userName, password string) (*ResourcesContainerModel, error) {
server := serverData{nsxServer, userName, password}
res := NewResourcesContainerModel()
resources.FixResourcesCode()
err := collectResultList(server, virtualMachineQuery, &res.VirtualMachineList)
if err != nil {
return nil, err
}
err = collectResultList(server, virtualInterfaceQuery, &res.VirtualNetworkInterfaceList)
if err != nil {
return nil, err
}
err = collectResultList(server, servicesQuery, &res.ServiceList)
if err != nil {
return nil, err
Expand All @@ -46,7 +57,21 @@ func CollectResources(nsxServer, userName, password string) (*ResourcesContainer
if err != nil {
return nil, err
}

for si := range res.SegmentList {
segmentID := *res.SegmentList[si].Id
err = collectResultList(server, fmt.Sprintf(segmentPortsQuery, segmentID), &res.SegmentList[si].SegmentPorts)
if err != nil {
return nil, err
}
}
err = collectResultList(server, tier0Query, &res.Tier0List)
if err != nil {
return nil, err
}
err = collectResultList(server, tier1Query, &res.Tier1List)
if err != nil {
return nil, err
}
for di := range res.DomainList {
domainID := *res.DomainList[di].Id
domainResources := &res.DomainList[di].Resources
Expand Down
90 changes: 90 additions & 0 deletions pkg/collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"os"
"path"
"strings"
"testing"

"github.com/np-guard/vmware-analyzer/pkg/common"
Expand Down Expand Up @@ -56,6 +57,9 @@ func TestCollectResources(t *testing.T) {
if len(got.VirtualMachineList) == 0 {
t.Errorf("didnt find VirtualMachineList")
}
testTopology(got)
dotTopology(got)
dotConnections(got)
for _, service := range got.ServiceList {
for _, e := range service.ServiceEntries {
//nolint:errcheck // we do not support al services?
Expand Down Expand Up @@ -147,3 +151,89 @@ func TestCollectResources(t *testing.T) {
})
}
}

func testTopology(got *ResourcesContainerModel) {
for _, segment := range got.SegmentList {

Check failure on line 156 in pkg/collector/collector_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

rangeValCopy: each iteration copies 560 bytes (consider pointers or indexing) (gocritic)
fmt.Printf("--------------------- segment(type)[addr] %s ------------------\n", segmentName(&segment))

if segment.ConnectivityPath == nil {
fmt.Printf("segment(type)[addr] %s has no ConnectivityPath\n", segmentName(&segment))
} else if t1 := got.GetTier1(*segment.ConnectivityPath); t1 != nil {
t0 := got.GetTier0(*t1.Tier0Path)
fmt.Printf("[segment(type)[addr], t1, t0]: [%s, %s, %s]\n", segmentName(&segment), *t1.DisplayName, *t0.DisplayName)
} else if t0 := got.GetTier0(*segment.ConnectivityPath); t0 != nil {
fmt.Printf("[segment(type)[addr], t0]: [%s, %s]\n", segmentName(&segment), *t0.DisplayName)
} else {
fmt.Printf("fail to find tier of segment(type)[addr]: %s with connectivity %s\n", segmentName(&segment), *segment.ConnectivityPath)
}
if len(segment.SegmentPorts) == 0 {
fmt.Printf("segment(type)[addr] %s has no ports\n", segmentName(&segment))
}
for _, port := range segment.SegmentPorts {

Check failure on line 172 in pkg/collector/collector_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

rangeValCopy: each iteration copies 376 bytes (consider pointers or indexing) (gocritic)
att := *port.Attachment.Id
vif := got.GetVirtualNetworkInterfaceByPort(att)
fmt.Printf("[segment(type)[addr], vm]: [%s, %s]\n", segmentName(&segment), vniName(got, vif))
}
}
}


func dotTopology(got *ResourcesContainerModel) {
out := "digraph D {\n"
for _, t1 := range got.Tier1List {

Check failure on line 183 in pkg/collector/collector_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

rangeValCopy: each iteration copies 472 bytes (consider pointers or indexing) (gocritic)
t0 := got.GetTier0(*t1.Tier0Path)
out += fmt.Sprintf("\"t1:%s\" -> \"t0:%s\"\n", *t1.DisplayName, *t0.DisplayName)
}
for _, segment := range got.SegmentList {
if segment.ConnectivityPath == nil {
} else if t1 := got.GetTier1(*segment.ConnectivityPath); t1 != nil {
out += fmt.Sprintf("\"sg:%s\" -> \"t1:%s\"\n", segmentName(&segment), *t1.DisplayName)
} else if t0 := got.GetTier0(*segment.ConnectivityPath); t0 != nil {
out += fmt.Sprintf("\"sg:%s\" -> \"t0:%s\"\n", segmentName(&segment), *t0.DisplayName)
}
for _, port := range segment.SegmentPorts {
att := *port.Attachment.Id
vif := got.GetVirtualNetworkInterfaceByPort(att)
out += fmt.Sprintf("\"ni:%s\" -> \"sg:%s\"\n", vniName(got, vif), segmentName(&segment))
vm := got.GetVirtualMachine(*vif.OwnerVmId)
out += fmt.Sprintf("\"vm:%s\" -> \"ni:%s\"\n", *vm.DisplayName, vniName(got, vif))
}
}
out += "}\n"
common.WriteToFile(path.Join(outDir, "topology.dot"), out)

Check failure on line 203 in pkg/collector/collector_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `common.WriteToFile` is not checked (errcheck)
}

func dotConnections(got *ResourcesContainerModel) {
out := "digraph D {\n"

for i1 := range got.VirtualNetworkInterfaceList {
for i2 := range got.VirtualNetworkInterfaceList {
v1 := &got.VirtualNetworkInterfaceList[i1]
v2 := &got.VirtualNetworkInterfaceList[i2]
if i1 > i2 && IsConnected(got, v1, v2) {
out += fmt.Sprintf("\"%s\" -> \"%s\"[dir=none]\n", vniName(got, v1), vniName(got, v2))
}
}
}
out += "}\n"
common.WriteToFile(path.Join(outDir, "connection.dot"), out)

Check failure on line 219 in pkg/collector/collector_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `common.WriteToFile` is not checked (errcheck)
}


func vniName(resources *ResourcesContainerModel, vni *VirtualNetworkInterface) string {
addresses := []string{}
for _, ai := range vni.IpAddressInfo {
for _, a := range ai.IpAddresses {
addresses = append(addresses, string(a))
}
}
return fmt.Sprintf("%s\\n[%s]", *resources.GetVirtualMachine(*vni.OwnerVmId).DisplayName, strings.Join(addresses, ","))
}

func segmentName(segment *Segment) string {
nAddresses := []string{}
for _, subnet := range segment.Subnets {
nAddresses = append(nAddresses, *subnet.Network)
}
return fmt.Sprintf("%s(%s)\\nnetworks[%s]", *segment.DisplayName, *segment.Type, strings.Join(nAddresses, ","))
}
75 changes: 34 additions & 41 deletions pkg/collector/data_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,43 @@ func (s *Service) UnmarshalJSON(b []byte) error {
type VirtualMachine struct {
resources.VirtualMachine
}
type VirtualNetworkInterface struct {
resources.VirtualNetworkInterface
}
type Segment struct {
resources.Segment
SegmentPorts []SegmentPort `json:"segment_ports"`
}

func (d *Segment) UnmarshalJSON(b []byte) error {
var raw map[string]json.RawMessage
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
var res Segment
if err := json.Unmarshal(b, &res.Segment); err != nil {
return err
}
if m, ok := raw["segment_ports"]; ok {
if err := json.Unmarshal(m, &res.SegmentPorts); err != nil {
return err
}
}
*d = res
return nil
}

type SegmentPort struct {
resources.SegmentPort
}

type Tier0 struct {
resources.Tier0
}
type Tier1 struct {
resources.Tier1
}

type RealizedVirtualMachine struct {
resources.RealizedVirtualMachine
}
Expand Down Expand Up @@ -369,44 +403,3 @@ func (d *Domain) UnmarshalJSON(b []byte) error {
*d = res
return nil
}

// Helper function for unmarshalling
/*
func jsonToMap(jsonStr []byte) (map[string]json.RawMessage, error) {
var result map[string]json.RawMessage
err := json.Unmarshal(jsonStr, &result)
return result, err
}
func (res *Group) UnmarshalJSON(data []byte) error {
asObj := &resources.Group{}
err := json.Unmarshal(data, &asObj)
if err != nil {
return err
}
res.Group = *asObj
asMap, err := jsonToMap(data)
if err != nil {
return err
}
return json.Unmarshal(asMap["Members"], &res.Members)
}
*/
/*
func (res *NetworkACL) UnmarshalJSON(data []byte) error {
asMap, err := jsonToMap(data)
if err != nil {
return err
}
asObj := &vpcv1.NetworkACL{}
err = vpcv1.UnmarshalNetworkACL(asMap, &asObj)
if err != nil {
return err
}
res.NetworkACL = *asObj
return json.Unmarshal(data, &res.BaseTaggedResource)
}
*/
53 changes: 49 additions & 4 deletions pkg/collector/resources_container_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import (

// ResourcesContainerModel defines the model of a container for all resource types we can collect
type ResourcesContainerModel struct {
ServiceList []Service `json:"services"`
VirtualMachineList []VirtualMachine `json:"virtual_machines"`
SegmentList []Segment `json:"segments"`
DomainList []Domain `json:"domains"`
ServiceList []Service `json:"services"`
VirtualMachineList []VirtualMachine `json:"virtual_machines"`
VirtualNetworkInterfaceList []VirtualNetworkInterface `json:"virtual_network_interface"`
SegmentList []Segment `json:"segments"`
Tier0List []Tier0 `json:"tier0"`
Tier1List []Tier1 `json:"tier1"`
DomainList []Domain `json:"domains"`
}
type DomainResources struct {
SecurityPolicyList []SecurityPolicy `json:"security_policies"`
Expand Down Expand Up @@ -49,3 +52,45 @@ func (resources *ResourcesContainerModel) GetService(query string) *Service {
i := slices.IndexFunc(resources.ServiceList, func(gr Service) bool { return query == *gr.Path })
return &resources.ServiceList[i]
}

func (resources *ResourcesContainerModel) GetVirtualNetworkInterfaceByPort(portID string) *VirtualNetworkInterface {
i := slices.IndexFunc(resources.VirtualNetworkInterfaceList, func(vni VirtualNetworkInterface) bool {
return vni.LportAttachmentId != nil && portID == *vni.LportAttachmentId
})
return &resources.VirtualNetworkInterfaceList[i]
}

func (resources *ResourcesContainerModel) GetVirtualMachine(id string) *VirtualMachine {
i := slices.IndexFunc(resources.VirtualMachineList, func(vm VirtualMachine) bool { return id == *vm.ExternalId })
return &resources.VirtualMachineList[i]
}

func (resources *ResourcesContainerModel) GetTier0(query string) *Tier0 {
i := slices.IndexFunc(resources.Tier0List, func(t Tier0) bool { return query == *t.Path })
if i >= 0 {
return &resources.Tier0List[i]
}
return nil
}
func (resources *ResourcesContainerModel) GetTier1(query string) *Tier1 {
i := slices.IndexFunc(resources.Tier1List, func(t Tier1) bool { return query == *t.Path })
if i >= 0 {
return &resources.Tier1List[i]
}
return nil
}

func (resources *ResourcesContainerModel) GetSegment(query string) *Segment {
i := slices.IndexFunc(resources.SegmentList, func(t Segment) bool { return query == *t.Path })
return &resources.SegmentList[i]
}

func (resources *ResourcesContainerModel) GetSegmentPort(id string) *SegmentPort {
for _, segment := range resources.SegmentList {

Check failure on line 89 in pkg/collector/resources_container_model.go

View workflow job for this annotation

GitHub Actions / golangci-lint

rangeValCopy: each iteration copies 560 bytes (consider pointers or indexing) (gocritic)
i := slices.IndexFunc(segment.SegmentPorts, func(s SegmentPort) bool { return id == *s.Attachment.Id })
if i >= 0 {
return &segment.SegmentPorts[i]
}
}
return nil
}
74 changes: 74 additions & 0 deletions pkg/collector/topologyTree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Copyright 2023- IBM Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package collector


type treeNode interface {
parent(resources *ResourcesContainerModel) treeNode
}

////////////////////////////////////////////////////////////////////////////

func (v *VirtualNetworkInterface) parent(resources *ResourcesContainerModel) treeNode {
if v.LportAttachmentId == nil {
return nil
}
s := resources.GetSegmentPort(*v.LportAttachmentId)
if s == nil {
return nil
}
return s
}
func (s *SegmentPort) parent(resources *ResourcesContainerModel) treeNode {
return resources.GetSegment(*s.ParentPath)
}
func (s *Segment) parent(resources *ResourcesContainerModel) treeNode {
if s.ConnectivityPath == nil {
return nil
}
if t1 := resources.GetTier1(*s.ConnectivityPath); t1 != nil {
return t1
}
return resources.GetTier0(*s.ConnectivityPath)
}
func (t *Tier1) parent(resources *ResourcesContainerModel) treeNode {
return resources.GetTier0(*t.Tier0Path)
}
func (t *Tier0) parent(resources *ResourcesContainerModel) treeNode { return nil }


// //////////////////////////////////////////////////////////////////////
type treeNodeBranch []treeNode

func branch(resources *ResourcesContainerModel, n treeNode) treeNodeBranch {
if n == nil {
return treeNodeBranch{}
}
p := n.parent(resources)
return append(branch(resources, p), n)
}

func treeNodesPath(got *ResourcesContainerModel, t1, t2 treeNode) (bool, treeNode, treeNodeBranch, treeNodeBranch) {

Check failure on line 55 in pkg/collector/topologyTree.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unnamedResult: consider giving a name to these results (gocritic)
b1 := branch(got, t1)
b2 := branch(got, t2)
if b1[0] != b2[0] {
return false, nil, nil, nil
}
rootIndex := 0
for i := range b1 {
if b1[i] != b2[i] {
break
}
rootIndex = i
}
return true, b1[rootIndex], b1[rootIndex+1:], b2[rootIndex+1:]
}

func IsConnected(got *ResourcesContainerModel, t1, t2 treeNode) bool {
c, _,_,_ := treeNodesPath(got, t1, t2);

Check failure on line 72 in pkg/collector/topologyTree.go

View workflow job for this annotation

GitHub Actions / golangci-lint

declaration has 3 blank identifiers (dogsled)
return c
}
Loading

0 comments on commit 3359094

Please sign in to comment.