open-nomad/command/agent/csi_endpoint_test.go

171 lines
5.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package agent
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/nomad/state"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
)
func TestHTTP_CSIEndpointPlugin(t *testing.T) {
ci.Parallel(t)
httpTest(t, nil, func(s *TestAgent) {
server := s.Agent.Server()
cleanup := state.CreateTestCSIPlugin(server.State(), "foo")
defer cleanup()
body := bytes.NewBuffer(nil)
req, err := http.NewRequest(http.MethodGet, "/v1/plugin/csi/foo", body)
require.NoError(t, err)
resp := httptest.NewRecorder()
obj, err := s.Server.CSIPluginSpecificRequest(resp, req)
require.NoError(t, err)
require.Equal(t, 200, resp.Code)
out, ok := obj.(*structs.CSIPlugin)
require.True(t, ok)
// ControllersExpected is 0 because this plugin was created without a job,
// which sets expected
require.Equal(t, 0, out.ControllersExpected)
require.Equal(t, 1, out.ControllersHealthy)
require.Len(t, out.Controllers, 1)
require.Equal(t, 0, out.NodesExpected)
require.Equal(t, 2, out.NodesHealthy)
require.Len(t, out.Nodes, 2)
})
}
func TestHTTP_CSIParseSecrets(t *testing.T) {
ci.Parallel(t)
testCases := []struct {
val string
expect structs.CSISecrets
}{
{"", nil},
{"one", nil},
{"one,two", nil},
{"one,two=value_two",
structs.CSISecrets(map[string]string{"two": "value_two"})},
{"one=value_one,one=overwrite",
structs.CSISecrets(map[string]string{"one": "overwrite"})},
{"one=value_one,two=value_two",
structs.CSISecrets(map[string]string{"one": "value_one", "two": "value_two"})},
{"one=value_one=two,two=value_two",
structs.CSISecrets(map[string]string{"one": "value_one=two", "two": "value_two"})},
}
for _, tc := range testCases {
req, _ := http.NewRequest(http.MethodGet, "/v1/plugin/csi/foo", nil)
req.Header.Add("X-Nomad-CSI-Secrets", tc.val)
require.Equal(t, tc.expect, parseCSISecrets(req), tc.val)
}
}
func TestHTTP_CSIEndpointRegisterVolume(t *testing.T) {
ci.Parallel(t)
httpTest(t, nil, func(s *TestAgent) {
server := s.Agent.Server()
cleanup := state.CreateTestCSIPluginNodeOnly(server.State(), "foo")
defer cleanup()
args := structs.CSIVolumeRegisterRequest{
Volumes: []*structs.CSIVolume{{
ID: "bar",
PluginID: "foo",
RequestedCapabilities: []*structs.CSIVolumeCapability{{
AccessMode: structs.CSIVolumeAccessModeMultiNodeSingleWriter,
AttachmentMode: structs.CSIVolumeAttachmentModeFilesystem,
}},
}},
}
body := encodeReq(args)
req, err := http.NewRequest(http.MethodPut, "/v1/volumes", body)
require.NoError(t, err)
resp := httptest.NewRecorder()
_, err = s.Server.CSIVolumesRequest(resp, req)
require.NoError(t, err, "put error")
req, err = http.NewRequest(http.MethodGet, "/v1/volume/csi/bar", nil)
require.NoError(t, err)
resp = httptest.NewRecorder()
raw, err := s.Server.CSIVolumeSpecificRequest(resp, req)
require.NoError(t, err, "get error")
out, ok := raw.(*structs.CSIVolume)
require.True(t, ok)
require.Equal(t, 1, out.ControllersHealthy)
require.Equal(t, 2, out.NodesHealthy)
req, err = http.NewRequest(http.MethodDelete, "/v1/volume/csi/bar/detach", nil)
require.NoError(t, err)
resp = httptest.NewRecorder()
_, err = s.Server.CSIVolumeSpecificRequest(resp, req)
require.Equal(t, CodedError(400, "detach requires node ID"), err)
})
}
func TestHTTP_CSIEndpointCreateVolume(t *testing.T) {
ci.Parallel(t)
httpTest(t, nil, func(s *TestAgent) {
server := s.Agent.Server()
cleanup := state.CreateTestCSIPlugin(server.State(), "foo")
defer cleanup()
args := structs.CSIVolumeCreateRequest{
Volumes: []*structs.CSIVolume{{
ID: "baz",
PluginID: "foo",
RequestedCapabilities: []*structs.CSIVolumeCapability{{
AccessMode: structs.CSIVolumeAccessModeMultiNodeSingleWriter,
AttachmentMode: structs.CSIVolumeAttachmentModeFilesystem,
}},
}},
}
body := encodeReq(args)
req, err := http.NewRequest(http.MethodPut, "/v1/volumes/create", body)
require.NoError(t, err)
resp := httptest.NewRecorder()
_, err = s.Server.CSIVolumesRequest(resp, req)
require.Error(t, err, "controller validate volume: No path to node")
req, err = http.NewRequest(http.MethodDelete, "/v1/volume/csi/baz", nil)
require.NoError(t, err)
resp = httptest.NewRecorder()
_, err = s.Server.CSIVolumeSpecificRequest(resp, req)
require.Error(t, err, "volume not found: baz")
})
}
func TestHTTP_CSIEndpointSnapshot(t *testing.T) {
ci.Parallel(t)
httpTest(t, nil, func(s *TestAgent) {
server := s.Agent.Server()
cleanup := state.CreateTestCSIPlugin(server.State(), "foo")
defer cleanup()
args := &api.CSISnapshotCreateRequest{
Snapshots: []*api.CSISnapshot{{
Name: "snap-*",
PluginID: "foo",
SourceVolumeID: "bar",
}},
}
body := encodeReq(args)
req, err := http.NewRequest(http.MethodPut, "/v1/volumes/snapshot", body)
require.NoError(t, err)
resp := httptest.NewRecorder()
_, err = s.Server.CSISnapshotsRequest(resp, req)
require.Error(t, err, "no such volume: bar")
})
}