Skip to content

Commit

Permalink
Upgrade Octant to v0.16.1 & Add alerts for traceflow UI
Browse files Browse the repository at this point in the history
  • Loading branch information
ZhangYW18 committed Oct 16, 2020
1 parent 9cf317b commit 94392ef
Show file tree
Hide file tree
Showing 5 changed files with 420 additions and 85 deletions.
8 changes: 4 additions & 4 deletions build/images/Dockerfile.octant.ubuntu
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ LABEL description="A docker image to deploy octant and antrea related octant plu

USER root

# Get and install octant v0.13.1
# Get and install octant v0.16.1
RUN apt-get update && \
apt-get install -y --no-install-recommends wget ca-certificates && \
wget -q https://github.com/vmware-tanzu/octant/releases/download/v0.13.1/octant_0.13.1_Linux-64bit.deb && \
dpkg -i octant_0.13.1_Linux-64bit.deb && \
wget -q https://github.com/vmware-tanzu/octant/releases/download/v0.16.1/octant_0.16.1_Linux-64bit.deb && \
dpkg -i octant_0.16.1_Linux-64bit.deb && \
apt-get remove -y wget ca-certificates && \
rm -rf octant_0.13.1_Linux-64bit.deb /var/cache/apt/* /var/lib/apt/lists/*
rm -rf octant_0.16.1_Linux-64bit.deb /var/cache/apt/* /var/lib/apt/lists/*

# Install octant plugin for Antrea UI display
RUN mkdir -p /root/.config/plugins/octant
Expand Down
12 changes: 6 additions & 6 deletions docs/octant-plugin-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,17 @@ detailed installation instructions.

You can follow the steps listed below to install octant and antrea-octant-plugin on linux.

1. Get and install Octant v0.13.1.
1. Get and install Octant v0.16.1.

Depending on your linux operating system, to install Octant v0.13.1, you can use either
Depending on your linux operating system, to install Octant v0.16.1, you can use either
```bash
wget https://github.com/vmware-tanzu/octant/releases/download/v0.13.1/octant_0.13.1_Linux-64bit.deb
dpkg -i octant_0.13.1_Linux-64bit.deb
wget https://github.com/vmware-tanzu/octant/releases/download/v0.16.1/octant_0.16.1_Linux-64bit.deb
dpkg -i octant_0.16.1_Linux-64bit.deb
```
or
```bash
wget https://github.com/vmware-tanzu/octant/releases/download/v0.13.1/octant_0.13.1_Linux-64bit.rpm
rpm -i octant_0.13.1_Linux-64bit.rpm
wget https://github.com/vmware-tanzu/octant/releases/download/v0.16.1/octant_0.16.1_Linux-64bit.rpm
rpm -i octant_0.16.1_Linux-64bit.rpm
```

2. Export your kubeconfig path (file location depends on your setup) to environment variable $KUBECONFIG.
Expand Down
108 changes: 96 additions & 12 deletions plugins/octant/cmd/antrea-octant-plugin/traceflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ package main

import (
"context"
"fmt"
"log"
"net"
"regexp"
"sort"
"strconv"
"time"

"github.com/vmware-tanzu/octant/pkg/action"
"github.com/vmware-tanzu/octant/pkg/plugin/service"
"github.com/vmware-tanzu/octant/pkg/view/component"
"github.com/vmware-tanzu/octant/pkg/view/flexlayout"
Expand Down Expand Up @@ -55,8 +57,8 @@ const (
ageCol = "Age"
traceNameCol = "Trace Name"

namespaceStrPattern = `[a-z0-9]([-a-z0-9]*[a-z0-9])?`
podStrPattern = `[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*`
namespaceStrPattern = `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
podStrPattern = `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`

TIME_FORMAT_YYYYMMDD_HHMMSS = "20060102-150405"
)
Expand Down Expand Up @@ -92,7 +94,7 @@ func getDstType(tf *opsv1alpha1.Traceflow) string {
func regExpMatch(pattern, str string) bool {
match, err := regexp.MatchString(pattern, str)
if err != nil {
log.Printf("Failed to judge srcPod string pattern: %s", err)
log.Printf("Failed to judge string pattern: %s", err)
return false
}
if !match {
Expand All @@ -112,55 +114,88 @@ func (p *antreaOctantPlugin) actionHandler(request *service.ActionRequest) error

switch actionName {
case addTfAction:
// TODO Octant v0.13.1 does not support alerts, sending alerts is supported with Octant no earlier than v0.16.0.
// TODO After upgrading Octant, send alerts when one of the sanity checks for users' input fail to pass.
srcNamespace, err := request.Payload.String(srcNamespaceCol)
if err != nil {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get srcNamespace as string: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to get source namespace as "+
"string: %s", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
if match := regExpMatch(namespaceStrPattern, srcNamespace); !match {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to match namespace %s with K8s Namespace pattern %s", srcNamespace, namespaceStrPattern)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Invalid source namespace string, "+
"please check your input and submit again."), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}

srcPod, err := request.Payload.String(srcPodCol)
if err != nil {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get srcPod as string: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to get source pod as "+
"string: %s", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
if match := regExpMatch(podStrPattern, srcPod); !match {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to match pod name %s with K8s Pod pattern %s", srcPod, podStrPattern)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Invalid source pod string, "+
"please check your input and submit again."), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}

// Judge the destination type and get destination according to the type.
dstType, err := request.Payload.StringSlice(dstTypeCol)
if err != nil || len(dstType) == 0 {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get dstType as string slice: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Invalid destination type chioce, "+
"please check your input and submit again."), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
log.Printf("dstType %+v", dstType)
dst, err := request.Payload.String(dstCol)
if err != nil {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get dst as string: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to get destination as "+
"string: %s", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
dstNamespace, err := request.Payload.OptionalString(dstNamespaceCol)
if err != nil {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get dstNamespace as string: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to get destination namespace as "+
"string: %s", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
var destination opsv1alpha1.Destination
switch dstType[0] {
case opsv1alpha1.DstTypePod:
if match := regExpMatch(podStrPattern, dst); !match {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to match pod name %s with K8s Pod pattern %s", dst, podStrPattern)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Invalid destination pod string, "+
"please check your input and submit again."), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
if match := regExpMatch(namespaceStrPattern, dstNamespace); !match {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to match namespace %s with K8s Namespace pattern %s", dstNamespace, namespaceStrPattern)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Invalid destination namespace string, "+
"please check your input and submit again."), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
destination = opsv1alpha1.Destination{
Namespace: dstNamespace,
Expand All @@ -171,10 +206,18 @@ func (p *antreaOctantPlugin) actionHandler(request *service.ActionRequest) error
if s == nil {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get destination IP as a valid IPv4 IP: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Invalid destination IPv4 string, "+
"please check your input and submit again."), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
if s.To4() == nil {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get destination IP as a valid IPv4 IP: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Invalid destination IPv4 string, "+
"please check your input and submit again."), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
destination = opsv1alpha1.Destination{
IP: dst,
Expand All @@ -183,28 +226,40 @@ func (p *antreaOctantPlugin) actionHandler(request *service.ActionRequest) error
if match := regExpMatch(namespaceStrPattern, dstNamespace); !match {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to match namespace %s with K8s Namespace pattern %s", dstNamespace, namespaceStrPattern)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Invalid destination namespace string, "+
"please check your input and submit again."), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
destination = opsv1alpha1.Destination{
Namespace: dstNamespace,
Service: dst,
}
}

// It is not required for users to input port numbers.
hasSrcPort, hasDstPort := true, true
srcPort, err := request.Payload.Uint16(srcPortCol)
if err != nil {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get srcPort as int: %s", err)
hasSrcPort = false
}
dstPort, err := request.Payload.Uint16(dstPortCol)
if err != nil {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get dstPort as int: %s", err)
hasDstPort = false
}

protocol, err := request.Payload.StringSlice(protocolCol)
if err != nil || len(protocol) == 0 {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"failed to get protocol as string slice: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to get protocol as "+
"string: %s", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}

// Judge whether the name of trace flow is duplicated.
Expand All @@ -215,6 +270,10 @@ func (p *antreaOctantPlugin) actionHandler(request *service.ActionRequest) error
if tfOld.Name == tfName {
log.Printf("Invalid user input, CRD creation or Traceflow request may fail: "+
"duplicate traceflow \"%s\": same source pod and destination pod in less than one second: %+v. ", tfName, tfOld)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Duplicate traceflow: same source pod "+
"and destination pod in less than one second"), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}

tf := &opsv1alpha1.Traceflow{
Expand All @@ -239,16 +298,23 @@ func (p *antreaOctantPlugin) actionHandler(request *service.ActionRequest) error
case opsv1alpha1.TCPProtocol:
{
tf.Spec.Packet.TransportHeader.TCP = &opsv1alpha1.TCPHeader{
SrcPort: int32(srcPort),
DstPort: int32(dstPort),
Flags: 2,
Flags: 2,
}
if hasSrcPort {
tf.Spec.Packet.TransportHeader.TCP.SrcPort = int32(srcPort)
}
if hasDstPort {
tf.Spec.Packet.TransportHeader.TCP.DstPort = int32(dstPort)
}
}
case opsv1alpha1.UDPProtocol:
{
tf.Spec.Packet.TransportHeader.UDP = &opsv1alpha1.UDPHeader{
SrcPort: int32(srcPort),
DstPort: int32(dstPort),
tf.Spec.Packet.TransportHeader.UDP = &opsv1alpha1.UDPHeader{}
if hasSrcPort {
tf.Spec.Packet.TransportHeader.UDP.SrcPort = int32(srcPort)
}
if hasDstPort {
tf.Spec.Packet.TransportHeader.UDP.DstPort = int32(dstPort)
}
}
case opsv1alpha1.ICMPProtocol:
Expand All @@ -263,9 +329,15 @@ func (p *antreaOctantPlugin) actionHandler(request *service.ActionRequest) error
tf, err = p.client.OpsV1alpha1().Traceflows().Create(ctx, tf, v1.CreateOptions{})
if err != nil {
log.Printf("Failed to create traceflow CRD \"%s\", err: %s", tfName, err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to create traceflow CRD, "+
"err: %s", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
log.Printf("Create traceflow CRD \"%s\" successfully, Traceflow Results: %+v", tfName, tf)
alert := action.CreateAlert(action.AlertTypeSuccess, fmt.Sprintf("Traceflow \"%s\" is created successfully",
tfName), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
// Automatically delete the traceflow CRD after created for 300s(5min).
go func(tfName string) {
age := time.Second * 300
Expand All @@ -281,27 +353,39 @@ func (p *antreaOctantPlugin) actionHandler(request *service.ActionRequest) error
p.graph, err = graphviz.GenGraph(p.lastTf)
if err != nil {
log.Printf("Failed to generate traceflow graph \"%s\", err: %s", tfName, err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to generate traceflow graph, "+
"err: %s", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
return nil
case showGraphAction:
name, err := request.Payload.String(traceNameCol)
if err != nil {
log.Printf("Failed to get name at string: %s", err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Duplicate traceflow: same source pod "+
"and destination pod in less than one second"), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
// Invoke GenGraph to show
ctx := context.Background()
tf, err := p.client.OpsV1alpha1().Traceflows().Get(ctx, name, v1.GetOptions{})
if err != nil {
log.Printf("Failed to get traceflow CRD \"%s\", err: %s ", name, err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to get traceflow CRD, "+
"err: %s ", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
log.Printf("Get traceflow CRD \"%s\" successfully, Traceflow Results: %+v", name, tf)
p.lastTf = tf
p.graph, err = graphviz.GenGraph(p.lastTf)
if err != nil {
log.Printf("Failed to generate traceflow graph \"%s\", err: %s", name, err)
alert := action.CreateAlert(action.AlertTypeError, fmt.Sprintf("Failed to generate traceflow graph, "+
"err: %s", err), action.DefaultAlertExpiration)
request.DashboardClient.SendAlert(request.Context(), request.ClientID, alert)
return nil
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions plugins/octant/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ go 1.13

require (
github.com/vmware-tanzu/antrea v0.0.0
github.com/vmware-tanzu/octant v0.13.1
k8s.io/apimachinery v0.19.0-alpha.3
github.com/vmware-tanzu/octant v0.16.1
k8s.io/apimachinery v0.19.0-beta.2
k8s.io/client-go v0.19.0-alpha.3
)

Expand Down
Loading

0 comments on commit 94392ef

Please sign in to comment.