mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
when templating the output of the namespace connectivity check we were
referring to the 'fromCIDR' as 'fromNamespace'. it makes way more sense
to refer to it as 'fromCIDR' as this is how it is provided in the input
for the collector.
as this is a brand new feature it is very unlikely that anyone is using
this feature (except for the embedded cluster that still needs to be
patched accodringly).
this is how the analyser were defined before:
```yaml
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: ec-cluster-preflight
spec:
analyzers:
- networkNamespaceConnectivity:
collectorName: check-network-connectivity
outcomes:
- pass:
message: "Communication between {{ .FromNamespace }} and {{ .ToNamespace }} is working"
- fail:
message: "{{ .ErrorMessage }}"
```
and this is how it is now:
```yaml
apiVersion: troubleshoot.sh/v1beta2
kind: HostPreflight
metadata:
name: ec-cluster-preflight
spec:
analyzers:
- networkNamespaceConnectivity:
collectorName: check-network-connectivity
outcomes:
- pass:
message: "Communication between {{ .FromCIDR }} and {{ .ToCIDR }} is working"
- fail:
message: "{{ .ErrorMessage }}"
```
232 lines
7.4 KiB
Go
232 lines
7.4 KiB
Go
package collect
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
|
"github.com/replicatedhq/troubleshoot/pkg/namespaces"
|
|
)
|
|
|
|
// NetworkNamespaceConnectivityInfo is the output of this collector, here we
|
|
// have the logs, the information from the source and destination namespaces,
|
|
// errors and a success flag.
|
|
type NetworkNamespaceConnectivityInfo struct {
|
|
FromCIDR string `json:"from_cidr"`
|
|
ToCIDR string `json:"to_cidr"`
|
|
Errors NetworkNamespaceConnectivityErrors `json:"errors"`
|
|
Output NetworkNamespaceConnectivityOutput `json:"output"`
|
|
Success bool `json:"success"`
|
|
}
|
|
|
|
// ErrorMessage returns the error message from the errors field.
|
|
func (n *NetworkNamespaceConnectivityInfo) ErrorMessage() string {
|
|
return n.Errors.Errors()
|
|
}
|
|
|
|
// NetworkNamespaceConnectivityErrors is a struct that contains the errors that
|
|
// occurred during the network namespace connectivity test
|
|
type NetworkNamespaceConnectivityErrors struct {
|
|
FromCIDRCreation string `json:"from_cidr_creation"`
|
|
ToCIDRCreation string `json:"to_cidr_creation"`
|
|
UDPClient string `json:"udp_client"`
|
|
UDPServer string `json:"udp_server"`
|
|
TCPClient string `json:"tcp_client"`
|
|
TCPServer string `json:"tcp_server"`
|
|
}
|
|
|
|
// Errors returns a string representation of the errors found during the
|
|
// network namespace connectivity test.
|
|
func (e NetworkNamespaceConnectivityErrors) Errors() string {
|
|
var sb strings.Builder
|
|
if e.FromCIDRCreation != "" {
|
|
sb.WriteString("Failed to create 'from' namespace: ")
|
|
sb.WriteString(e.FromCIDRCreation + "\n")
|
|
}
|
|
|
|
if e.ToCIDRCreation != "" {
|
|
sb.WriteString("Failed to create 'to' namespace: ")
|
|
sb.WriteString(e.ToCIDRCreation + "\n")
|
|
}
|
|
|
|
if e.UDPClient != "" {
|
|
sb.WriteString("UDP connection failed with: ")
|
|
sb.WriteString(e.UDPClient + "\n")
|
|
}
|
|
|
|
if e.UDPServer != "" {
|
|
sb.WriteString("UDP server failed with: ")
|
|
sb.WriteString(e.UDPServer + "\n")
|
|
}
|
|
|
|
if e.TCPClient != "" {
|
|
sb.WriteString("TCP connection failed with: ")
|
|
sb.WriteString(e.TCPClient + "\n")
|
|
}
|
|
|
|
if e.TCPServer != "" {
|
|
sb.WriteString("TCP server failed with: ")
|
|
sb.WriteString(e.TCPServer + "\n")
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// NetworkNamespaceConnectivityOutput is a struct that contains the logs from
|
|
// the network namespace connectivity collector.
|
|
type NetworkNamespaceConnectivityOutput struct {
|
|
mtx sync.Mutex
|
|
Logs []string `json:"logs"`
|
|
}
|
|
|
|
// Printf is a method that allows us to print the logs directly into a slice.
|
|
func (l *NetworkNamespaceConnectivityOutput) Printf(format string, v ...interface{}) {
|
|
l.mtx.Lock()
|
|
defer l.mtx.Unlock()
|
|
|
|
format = fmt.Sprintf("[%s] %s", time.Now().Format(time.RFC3339), format)
|
|
l.Logs = append(l.Logs, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
// CollectHostNetworkNamespaceConnectivity collects information about the
|
|
// capability of the host to route traffic between two different network
|
|
// namespaces. This collector will create two network namespaces and attempt to
|
|
// issue TCP and UDP requests between them.
|
|
type CollectHostNetworkNamespaceConnectivity struct {
|
|
hostCollector *troubleshootv1beta2.HostNetworkNamespaceConnectivity
|
|
BundlePath string
|
|
}
|
|
|
|
// Title returns the title of the collector.
|
|
func (c *CollectHostNetworkNamespaceConnectivity) Title() string {
|
|
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Host Network Namespace Connectivity")
|
|
}
|
|
|
|
// IsExcluded returns true if the collector should be excluded.
|
|
func (c *CollectHostNetworkNamespaceConnectivity) IsExcluded() (bool, error) {
|
|
return isExcluded(c.hostCollector.Exclude)
|
|
}
|
|
|
|
// marshal marshals the network namespace connectivity info into a JSON file,
|
|
// writes it to the bundle path and returns the file name and the data.
|
|
func (c *CollectHostNetworkNamespaceConnectivity) marshal(info *NetworkNamespaceConnectivityInfo) (map[string][]byte, error) {
|
|
collectorName := c.hostCollector.CollectorName
|
|
if collectorName == "" {
|
|
collectorName = "networkNamespaceConnectivity"
|
|
}
|
|
name := filepath.Join("host-collectors/system", collectorName+".json")
|
|
|
|
data, err := json.MarshalIndent(info, "", " ")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal network namespace connectivity info: %w", err)
|
|
}
|
|
|
|
output := NewResult()
|
|
output.SaveResult(c.BundlePath, name, bytes.NewBuffer(data))
|
|
|
|
return map[string][]byte{
|
|
name: data,
|
|
}, nil
|
|
}
|
|
|
|
// validateCIDRs validates both the from and to CIDRs. They must be provided,
|
|
// be different and be valid CIDRs.
|
|
func (c *CollectHostNetworkNamespaceConnectivity) validateCIDRs() error {
|
|
if c.hostCollector.FromCIDR == "" || c.hostCollector.ToCIDR == "" {
|
|
return fmt.Errorf("fromCIDR and toCIDR must be provided")
|
|
}
|
|
|
|
if c.hostCollector.FromCIDR == c.hostCollector.ToCIDR {
|
|
return fmt.Errorf("fromCIDR and toCIDR must be different")
|
|
}
|
|
|
|
if _, _, err := net.ParseCIDR(c.hostCollector.FromCIDR); err != nil {
|
|
return fmt.Errorf("%s is not a valid cidr: %w", c.hostCollector.FromCIDR, err)
|
|
}
|
|
|
|
if _, _, err := net.ParseCIDR(c.hostCollector.ToCIDR); err != nil {
|
|
return fmt.Errorf("%s is not a valid cidr: %w", c.hostCollector.ToCIDR, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Collect collects the network namespace connectivity information. This
|
|
// function expects both the from and to CIDRs to be provided and different
|
|
// from each other.
|
|
func (c *CollectHostNetworkNamespaceConnectivity) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
|
|
if err := c.validateCIDRs(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &NetworkNamespaceConnectivityInfo{
|
|
FromCIDR: c.hostCollector.FromCIDR,
|
|
ToCIDR: c.hostCollector.ToCIDR,
|
|
}
|
|
|
|
opts := []namespaces.Option{namespaces.WithLogf(result.Output.Printf)}
|
|
|
|
// if user has chosen to use a specific port, use it.
|
|
if c.hostCollector.Port != 0 {
|
|
opts = append(opts, namespaces.WithPort(c.hostCollector.Port))
|
|
}
|
|
|
|
// if user has chosen to use a specific timeout and it is valid, use it.
|
|
if c.hostCollector.Timeout != "" {
|
|
timeout, err := time.ParseDuration(c.hostCollector.Timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid timeout %s", c.hostCollector.Timeout)
|
|
}
|
|
result.Output.Printf("using user provided timeout of %q", c.hostCollector.Timeout)
|
|
opts = append(opts, namespaces.WithTimeout(timeout))
|
|
}
|
|
|
|
fromNS, err := namespaces.NewNamespacePinger("from", c.hostCollector.FromCIDR, opts...)
|
|
if err != nil {
|
|
result.Errors.ToCIDRCreation = err.Error()
|
|
return c.marshal(result)
|
|
}
|
|
defer fromNS.Close()
|
|
|
|
toNS, err := namespaces.NewNamespacePinger("to", c.hostCollector.ToCIDR, opts...)
|
|
if err != nil {
|
|
result.Errors.FromCIDRCreation = err.Error()
|
|
return c.marshal(result)
|
|
}
|
|
defer toNS.Close()
|
|
|
|
udpErrors, tcpErrors := make(chan error), make(chan error)
|
|
toNS.StartUDPEchoServer(udpErrors)
|
|
toNS.StartTCPEchoServer(tcpErrors)
|
|
|
|
success := true
|
|
if err := fromNS.PingUDP(toNS.InternalIP); err != nil {
|
|
result.Errors.UDPClient = err.Error()
|
|
success = false
|
|
}
|
|
|
|
if err := <-udpErrors; err != nil {
|
|
result.Errors.UDPServer = err.Error()
|
|
success = false
|
|
}
|
|
|
|
if err := fromNS.PingTCP(toNS.InternalIP); err != nil {
|
|
result.Errors.TCPClient = err.Error()
|
|
success = false
|
|
}
|
|
|
|
if err := <-tcpErrors; err != nil {
|
|
result.Errors.TCPServer = err.Error()
|
|
success = false
|
|
}
|
|
|
|
result.Success = success
|
|
result.Output.Printf("network namespace connectivity test finished")
|
|
return c.marshal(result)
|
|
}
|