Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 5 additions & 2 deletions cmd/smd/iface/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ See ochami-smd(1) for more details.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
// Check that all required args are passed
if !cmd.Flag("data").Changed {
if len(args) != 3 {
return cli.Errorf(cli.CodeUsage, "expected -d or >= 3 arguments (component id, mac address, network name, ip address), got %d", len(args))
if len(args) < 3 {
return cli.Errorf(cli.CodeUsage, "expected -d or >= 3 arguments (component id, mac address, network,ip pairs), got %d", len(args))
}
} else {
if len(args) > 0 {
Expand Down Expand Up @@ -89,6 +89,9 @@ See ochami-smd(1) for more details.`,
var nets []smd.EthernetIP
for i := 2; i < len(args); i++ {
tokens := strings.SplitN(args[i], ",", 2)
if len(tokens) != 2 || tokens[0] == "" {
return cli.Errorf(cli.CodeUsage, "invalid network,ip pair %q: expected a network name and IPv4 address separated by a comma", args[i])
}
if ip := net.ParseIP(tokens[1]); ip.To4() == nil {
return cli.Errorf(cli.CodeUsage, "invalid IP address: %s", tokens[1])
}
Expand Down
48 changes: 48 additions & 0 deletions cmd/smd_iface_errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package cmd
import (
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/openchami/ochami/internal/cli"
Expand Down Expand Up @@ -77,6 +78,53 @@ func TestSMDIfaceAdd_InvalidIP(t *testing.T) {
}
}

// TestSMDIfaceAdd_InvalidNetworkPairs verifies malformed pairs and missing
// positional arguments return usage errors without sending a request.
func TestSMDIfaceAdd_InvalidNetworkPairs(t *testing.T) {
t.Parallel()

tests := []struct {
name string
args []string
want string
}{
{"no arguments", nil, "expected -d or >= 3 arguments"},
{"component only", []string{"node"}, "expected -d or >= 3 arguments"},
{"no network", []string{"node", "de:ca:fc:0f:fe:ee"}, "expected -d or >= 3 arguments"},
{"missing comma", []string{"node", "de:ca:fc:0f:fe:ee", "external"}, "invalid network,ip pair"},
{"empty network", []string{"node", "de:ca:fc:0f:fe:ee", ",10.1.0.55"}, "invalid network,ip pair"},
{"empty IP", []string{"node", "de:ca:fc:0f:fe:ee", "external,"}, "invalid IP address"},
{"extra comma", []string{"node", "de:ca:fc:0f:fe:ee", "external,10.1.0.55,internal"}, "invalid IP address"},
{"IPv6", []string{"node", "de:ca:fc:0f:fe:ee", "external,2001:db8::1"}, "invalid IP address"},
{"malformed second pair", []string{"node", "de:ca:fc:0f:fe:ee", "external,10.1.0.55", "internal"}, "invalid network,ip pair"},
{"invalid second IP", []string{"node", "de:ca:fc:0f:fe:ee", "external,10.1.0.55", "internal,not-an-ip"}, "invalid IP address"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

var requests int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
w.WriteHeader(http.StatusCreated)
}))
defer srv.Close()

args := append([]string{"smd", "--ignore-config", "iface", "add", "--uri", srv.URL, "--token", "t"}, tc.args...)
res := runOchamiWithRuntime(t, args...)
if res.err == nil || !strings.Contains(res.err.Error(), tc.want) {
t.Errorf("error = %v, want %q", res.err, tc.want)
}
if res.exitCode != cli.CodeUsage {
t.Errorf("exit code = %d, want %d (%s)", res.exitCode, cli.CodeUsage, cli.CodeName(cli.CodeUsage))
}
if requests != 0 {
t.Errorf("request count = %d, want 0", requests)
}
})
}
}

// TestSMDIfaceAdd_HTTPError verifies a failing POST resolves to CodeHTTP via the
// per-item aggregation.
func TestSMDIfaceAdd_HTTPError(t *testing.T) {
Expand Down
47 changes: 47 additions & 0 deletions cmd/smd_iface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ package cmd
// smd_iface_errors_test.go.

import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/openchami/ochami/pkg/client/smd"
)

// TestSMDIfaceGet_Filters verifies the query builder emits the expected filter
Expand Down Expand Up @@ -188,6 +191,50 @@ func TestSMDIfaceAdd_ByFlags(t *testing.T) {
}
}

// TestSMDIfaceAdd_MultipleNetworks verifies multiple network,IP arguments produce
// one POST /Inventory/EthernetInterfaces with all addresses on one interface.
func TestSMDIfaceAdd_MultipleNetworks(t *testing.T) {
t.Parallel()

var got smd.EthernetInterface
var requests int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
if r.Method != http.MethodPost || r.URL.Path != "/Inventory/EthernetInterfaces" {
t.Errorf("request = %s %s, want POST /Inventory/EthernetInterfaces", r.Method, r.URL.Path)
}
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
t.Errorf("decode request: %v", err)
}
w.WriteHeader(http.StatusCreated)
}))
defer srv.Close()

res := runOchamiWithRuntime(t, "smd", "--ignore-config", "iface", "add", "--uri", srv.URL, "--token", "t",
"-D", "Node networks", "x3000c1s7b55n0", "de:ca:fc:0f:fe:ee", "external,10.1.0.55", "internal,172.16.0.55")
if res.err != nil {
t.Fatalf("unexpected error: %v (exit %d)", res.err, res.exitCode)
}
if requests != 1 {
t.Errorf("request count = %d, want 1", requests)
}
if got.ComponentID != "x3000c1s7b55n0" || got.MACAddress != "de:ca:fc:0f:fe:ee" || got.Description != "Node networks" {
t.Errorf("interface = %+v, want specified component, MAC and description", got)
}
want := []smd.EthernetIP{
{Network: "external", IPAddress: "10.1.0.55"},
{Network: "internal", IPAddress: "172.16.0.55"},
}
if len(got.IPAddresses) != len(want) {
t.Fatalf("IPAddresses = %+v, want %+v", got.IPAddresses, want)
}
for i := range want {
if got.IPAddresses[i] != want[i] {
t.Errorf("IPAddresses[%d] = %+v, want %+v", i, got.IPAddresses[i], want[i])
}
}
}

// TestSMDIfaceAdd_ByData verifies "add -d <payload>" issues a POST.
func TestSMDIfaceAdd_ByData(t *testing.T) {
t.Parallel()
Expand Down
101 changes: 101 additions & 0 deletions man/ochami-smd.1.sc
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,107 @@ Subcommands for this command are as follows:

Manage Ethernet interfaces.

Subcommands for this command are as follows:

*add* [-D _description_] _comp_id_ _mac_addr_ _net_name_,_ip_addr_...++
*add* [-f _format_] -d _data_++
*add* [-f _format_] -d @_path_++
*add* [-f _format_] -d @-
Add one or more new Ethernet interfaces to SMD.

In the first form of the command, the ID of the component the interface
belongs to (_comp_id_, usually an xname), the interface's MAC address
(_mac_addr_), and one or more pairs of network name and IPv4 address
delimited by a comma (_net_name_,_ip_addr_) are required. Pass each pair
as a separate argument to add multiple IP addresses to the interface.

In the second form of the command, raw data is passed as an argument to be
Comment thread
davidallendj marked this conversation as resolved.
Resolved
the payload.

In the third form of the command, a file containing the payload data (see
the *EthernetInterface* data structure above) is passed. This is convenient
in cases of dealing with many Ethernet interfaces at once.

In the fourth form of the command, the payload data is read from standard
input.

This command sends a POST request to SMD's /Inventory/EthernetInterfaces
endpoint. An access token is required.

This command accepts the following options:

*-d, --data* (_data_ | @_path_ | @-)
Specify raw _data_ to send, the _path_ to a file to read payload data
from, or to read the data from standard input (@-). The format of data
read in any of these forms is JSON by default unless *-f* is specified
to change it.

*-D, --description* _description_
Description of the Ethernet interface. Default: *Undescribed Ethernet
Interface*. Cannot be used with *-d*.

*-f, --format-input* _format_
Format of input payload data used by *-d*. Supported formats are:

- _json_ (default)
- _json-pretty_
- _yaml_

*delete* [--no-confirm] --all++
*delete* [--no-confirm] _iface_id_...++
*delete* [--no-confirm] [-f _format_] -d _data_++
*delete* [--no-confirm] [-f _format_] -d @_path_++
*delete* [--no-confirm] [-f _format_] -d @-
Delete one or more Ethernet interfaces in SMD. Unless *--no-confirm* is
passed, the user is asked to confirm deletion.

In the first form of the command, all Ethernet interfaces are deleted. *BE
CAREFUL!*

In the second form of the command, one or more Ethernet interface IDs
(_iface_id_) identifying the interface(s) to delete are specified. Note that
an Ethernet interface ID is not the same as a component xname.

In the third form of the command, raw data is passed as an argument to be
the payload.

In the fourth form of the command, a file containing the payload data (see
the *EthernetInterface* data structure above) is passed. This is convenient
in cases of dealing with many Ethernet interfaces at once.

In the fifth form of the command, the payload data is read from standard
input.

Only the ID of each payload item is used. If any ID, whether from the
payload or the command line, is blank, is "." or "..", or contains "/",
nothing is deleted and the command exits with a usage error. A missing ID
Comment thread
davidallendj marked this conversation as resolved.
Resolved
is not derived from *MACAddress*, so a payload item with only *MACAddress*
set also causes a usage error.

This command sends one or more DELETE requests to SMD's
/Inventory/EthernetInterfaces endpoint. An access token is required.

This command accepts the following options:

*-a, --all*
Delete *all* Ethernet interfaces in SMD. *BE CAREFUL!*

*-d, --data* (_data_ | @_path_ | @-)
Specify raw _data_ to send, the _path_ to a file to read payload data
from, or to read the data from standard input (@-). The format of data
read in any of these forms is JSON by default unless *-f* is specified
to change it.

*-f, --format-input* _format_
Format of input payload data used by *-d*. Supported formats are:

- _json_ (default)
- _json-pretty_
- _yaml_

*--no-confirm*
Do not ask the user to confirm deletion. Use with caution.

*get* [-F _format_] [--by-ip] [--comp-id _xname_,...] [-i _id_] [--ip _ip_,...] [-m _mac_,...] [--net _network_,...] [--newer-than _timestamp_] [--older-than _timestamp_] [--type _type_,...]
Aliases: *list*

Expand Down
Loading