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

cli: nomad agent does not show configured host_network #11432

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/11432.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
cli: the command `node status` now returns `host_network` information as well
```
9 changes: 9 additions & 0 deletions api/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,14 @@ type HostVolumeInfo struct {
ReadOnly bool
}

//HostNetworkInfo is used to return metadata about a given HostNetwork
type HostNetworkInfo struct {
Name string
CIDR string
Interface string
ReservedPorts string
}

type DrainStatus string

// DrainMetadata contains information about the most recent drain operation for a given Node.
Expand Down Expand Up @@ -541,6 +549,7 @@ type Node struct {
Events []*NodeEvent
Drivers map[string]*DriverInfo
HostVolumes map[string]*HostVolumeInfo
HostNetworks map[string]*HostNetworkInfo
CSIControllerPlugins map[string]*CSIInfo
CSINodePlugins map[string]*CSIInfo
LastDrain *DrainMetadata
Expand Down
8 changes: 8 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,14 @@ func (c *Client) setupNode() error {
}
}
}
if node.HostNetworks == nil {
if l := len(c.config.HostNetworks); l != 0 {
node.HostNetworks = make(map[string]*structs.ClientHostNetworkConfig, l)
for k, v := range c.config.HostNetworks {
node.HostNetworks[k] = v.Copy()
}
}
}

if node.Name == "" {
node.Name = node.ID
Expand Down
34 changes: 34 additions & 0 deletions command/node_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,16 @@ func nodeVolumeNames(n *api.Node) []string {
return volumes
}

func nodeNetworkNames(n *api.Node) []string {
var networks []string
for name := range n.HostNetworks {
networks = append(networks, name)
}

sort.Strings(networks)
return networks
}

func formatDrain(n *api.Node) string {
if n.DrainStrategy != nil {
b := new(strings.Builder)
Expand Down Expand Up @@ -400,6 +410,7 @@ func (c *NodeStatusCommand) formatNode(client *api.Client, node *api.Node) int {

if c.short {
basic = append(basic, fmt.Sprintf("Host Volumes|%s", strings.Join(nodeVolumeNames(node), ",")))
basic = append(basic, fmt.Sprintf("Host Networks|%s", strings.Join(nodeNetworkNames(node), ",")))
basic = append(basic, fmt.Sprintf("CSI Volumes|%s", strings.Join(nodeCSIVolumeNames(node, runningAllocs), ",")))
basic = append(basic, fmt.Sprintf("Drivers|%s", strings.Join(nodeDrivers(node), ",")))
c.Ui.Output(c.Colorize().Color(formatKV(basic)))
Expand Down Expand Up @@ -428,6 +439,7 @@ func (c *NodeStatusCommand) formatNode(client *api.Client, node *api.Node) int {
// driver info in the basic output
if !c.verbose {
basic = append(basic, fmt.Sprintf("Host Volumes|%s", strings.Join(nodeVolumeNames(node), ",")))
basic = append(basic, fmt.Sprintf("Host Networks|%s", strings.Join(nodeNetworkNames(node), ",")))
basic = append(basic, fmt.Sprintf("CSI Volumes|%s", strings.Join(nodeCSIVolumeNames(node, runningAllocs), ",")))
driverStatus := fmt.Sprintf("Driver Status| %s", c.outputTruncatedNodeDriverInfo(node))
basic = append(basic, driverStatus)
Expand All @@ -439,6 +451,7 @@ func (c *NodeStatusCommand) formatNode(client *api.Client, node *api.Node) int {
// If we're running in verbose mode, include full host volume and driver info
if c.verbose {
c.outputNodeVolumeInfo(node)
c.outputNodeNetworkInfo(node)
c.outputNodeCSIVolumeInfo(client, node, runningAllocs)
c.outputNodeDriverInfo(node)
}
Expand Down Expand Up @@ -544,6 +557,27 @@ func (c *NodeStatusCommand) outputNodeVolumeInfo(node *api.Node) {
}
}

func (c *NodeStatusCommand) outputNodeNetworkInfo(node *api.Node) {

names := make([]string, 0, len(node.HostNetworks))
for name := range node.HostNetworks {
names = append(names, name)
}
sort.Strings(names)

output := make([]string, 0, len(names)+1)
output = append(output, "Name|CIDR|Interface|ReservedPorts")

if len(names) > 0 {
c.Ui.Output(c.Colorize().Color("\n[bold]Host Networks"))
for _, hostNetworkName := range names {
info := node.HostNetworks[hostNetworkName]
output = append(output, fmt.Sprintf("%s|%v|%s|%s", hostNetworkName, info.CIDR, info.Interface, info.ReservedPorts))
}
c.Ui.Output(formatList(output))
}
}

func (c *NodeStatusCommand) outputNodeCSIVolumeInfo(client *api.Client, node *api.Node, runningAllocs []*api.Allocation) {

// Duplicate nodeCSIVolumeNames to sort by name but also index volume names to ids
Expand Down
123 changes: 123 additions & 0 deletions command/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package command

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/nomad/command/agent"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/mitchellh/cli"
"github.com/posener/complete"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestStatusCommand_Run_JobStatus(t *testing.T) {
Expand Down Expand Up @@ -233,3 +235,124 @@ func TestStatusCommand_AutocompleteArgs(t *testing.T) {
res := predictor.Predict(args)
assert.Contains(res, job.ID)
}

func TestStatusCommand_Run_HostNetwork(t *testing.T) {
t.Parallel()

ui := cli.NewMockUi()

testCases := []struct {
name string
clientHostNetworks []*structs.ClientHostNetworkConfig
verbose bool
assertions func(string)
}{
{
name: "short",
clientHostNetworks: []*structs.ClientHostNetworkConfig{{
Name: "internal",
CIDR: "127.0.0.1/8",
Interface: "lo",
}},
verbose: false,
assertions: func(out string) {
hostNetworksRegexpStr := `Host Networks\s+=\s+internal\n`
require.Regexp(t, regexp.MustCompile(hostNetworksRegexpStr), out)
},
},
{
name: "verbose",
clientHostNetworks: []*structs.ClientHostNetworkConfig{{
Name: "internal",
CIDR: "127.0.0.1/8",
Interface: "lo",
}},
verbose: true,
assertions: func(out string) {
verboseHostNetworksHeadRegexpStr := `Name\s+CIDR\s+Interface\s+ReservedPorts\n`
require.Regexp(t, regexp.MustCompile(verboseHostNetworksHeadRegexpStr), out)

verboseHostNetworksBodyRegexpStr := `internal\s+127\.0\.0\.1/8\s+lo\s+<none>\n`
require.Regexp(t, regexp.MustCompile(verboseHostNetworksBodyRegexpStr), out)
},
},
{
name: "verbose_nointerface",
clientHostNetworks: []*structs.ClientHostNetworkConfig{{
Name: "public",
CIDR: "10.199.0.200/24",
}},
verbose: true,
assertions: func(out string) {
verboseHostNetworksHeadRegexpStr := `Name\s+CIDR\s+Interface\s+ReservedPorts\n`
require.Regexp(t, regexp.MustCompile(verboseHostNetworksHeadRegexpStr), out)

verboseHostNetworksBodyRegexpStr := `public\s+10\.199\.0\.200/24\s+<none>\s+<none>\n`
require.Regexp(t, regexp.MustCompile(verboseHostNetworksBodyRegexpStr), out)
},
},
{
name: "verbose_nointerface_with_reservedports",
clientHostNetworks: []*structs.ClientHostNetworkConfig{{
Name: "public",
CIDR: "10.199.0.200/24",
ReservedPorts: "8080,8081",
}},
verbose: true,
assertions: func(out string) {
verboseHostNetworksHeadRegexpStr := `Name\s+CIDR\s+Interface\s+ReservedPorts\n`
require.Regexp(t, regexp.MustCompile(verboseHostNetworksHeadRegexpStr), out)

verboseHostNetworksBodyRegexpStr := `public\s+10\.199\.0\.200/24\s+<none>\s+8080,8081\n`
require.Regexp(t, regexp.MustCompile(verboseHostNetworksBodyRegexpStr), out)
},
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {

// Start in dev mode so we get a node registration
srv, client, url := testServer(t, true, func(c *agent.Config) {
c.Client.HostNetworks = tt.clientHostNetworks
})
defer srv.Shutdown()

cmd := &StatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}

// Wait for a node to appear
var nodeID string
testutil.WaitForResult(func() (bool, error) {
nodes, _, err := client.Nodes().List(nil)
if err != nil {
return false, err
}
if len(nodes) == 0 {
return false, fmt.Errorf("missing node")
}
nodeID = nodes[0].ID
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
})

// Query to check the node status
args := []string{"-address=" + url}
if tt.verbose {
args = append(args, "-verbose")
}
args = append(args, nodeID)

if code := cmd.Run(args); code != 0 {
t.Fatalf("expected exit 0, got: %d", code)
}

out := ui.OutputWriter.String()

tt.assertions(out)

ui.OutputWriter.Reset()
})
}

}
10 changes: 10 additions & 0 deletions nomad/structs/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,13 @@ type ClientHostNetworkConfig struct {
Interface string `hcl:"interface"`
ReservedPorts string `hcl:"reserved_ports"`
}

func (p *ClientHostNetworkConfig) Copy() *ClientHostNetworkConfig {
if p == nil {
return nil
}

c := new(ClientHostNetworkConfig)
*c = *p
return c
}
19 changes: 19 additions & 0 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,9 @@ type Node struct {
// HostVolumes is a map of host volume names to their configuration
HostVolumes map[string]*ClientHostVolumeConfig

// HostNetworks is a map of host host_network names to their configuration
HostNetworks map[string]*ClientHostNetworkConfig

// LastDrain contains metadata about the most recent drain operation
LastDrain *DrainMetadata

Expand Down Expand Up @@ -1993,6 +1996,7 @@ func (n *Node) Copy() *Node {
nn.CSINodePlugins = copyNodeCSI(nn.CSINodePlugins)
nn.Drivers = copyNodeDrivers(n.Drivers)
nn.HostVolumes = copyNodeHostVolumes(n.HostVolumes)
nn.HostNetworks = copyNodeHostNetworks(n.HostNetworks)
return nn
}

Expand Down Expand Up @@ -2054,6 +2058,21 @@ func copyNodeHostVolumes(volumes map[string]*ClientHostVolumeConfig) map[string]
return c
}

// copyNodeHostVolumes is a helper to copy a map of string to HostNetwork
func copyNodeHostNetworks(networks map[string]*ClientHostNetworkConfig) map[string]*ClientHostNetworkConfig {
l := len(networks)
if l == 0 {
return nil
}

c := make(map[string]*ClientHostNetworkConfig, l)
for network, v := range networks {
c[network] = v.Copy()
}

return c
}

// TerminalStatus returns if the current status is terminal and
// will no longer transition.
func (n *Node) TerminalStatus() bool {
Expand Down
7 changes: 7 additions & 0 deletions website/content/api-docs/nodes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,13 @@ $ curl \
"ReadOnly": false
}
},
"HostNetworks" : {
"public": {
"Name": "public",
"CIDR": "10.199.0.200/24",
"ReservedPorts": "8080,8081"
}
}
"ID": "1ac61e33-a465-2ace-f63f-cffa1285e7eb",
"LastDrain": {
"AccessorID": "4e1b7ce1-f8aa-d7ff-09f1-55c3a0fd3988",
Expand Down